All enemies within close range of each other die when I kill only one

Super weird bug I’m experiencing at the moment. For some odd reason that I cannot figure out (It’s been days). If the enemies in my game are all within a close range of each other when I attack and kill one, they all die. Hitbox isn’t the issue. My combat script isn’t the issue. Only the enemy I’m attacking takes damage but they all die when i kill the enemy i attacked. I’ve narrowed the issue down to this script but cannot figure out where the issue is stemming from. It’s a long script that I’m going clean up and break into modules soon. Anybody have an idea why this is happening? I’ve never seen a bug like this.

local PhysicsService = game:GetService("PhysicsService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")
local StarterGui = game:GetService("StarterGui")
local EnemyStatsModule = require(game.ServerScriptService:WaitForChild("Enemy Scripts"):WaitForChild("EnemyStatsModule"))

print("[DEBUG] EnemyHealthManager module is running!")

-- Function to award XP based on damage tracking
local function awardXPToContributors(enemy)
	local stats = EnemyStatsModule[enemy.Name]
	local damageTracking = enemy:FindFirstChild("DamageTracking")

	print("[XP DEBUG] Attempting to award XP for:", enemy.Name)
	print("[XP DEBUG] Stats:", 
		"Name:", enemy.Name, 
		"Level:", stats and stats.Level or "N/A", 
		"Base XP_Min:", stats and stats.XP_Min or "N/A", 
		"Base XP_Max:", stats and stats.XP_Max or "N/A"
	)
	print("[XP DEBUG] DamageTracking found:", damageTracking ~= nil)

	if stats and damageTracking then
		-- Get all players who dealt damage
		print("[XP DEBUG] Number of damage contributors:", #damageTracking:GetChildren())

		for _, playerValue in ipairs(damageTracking:GetChildren()) do
			local player = Players:FindFirstChild(playerValue.Name)
			print("[XP DEBUG] Found player:", player and player.Name or "nil")

			if player then
				local leaderstats = player:FindFirstChild("leaderstats")
				local xp = leaderstats and leaderstats:FindFirstChild("XP")

				print("[XP DEBUG] Found leaderstats:", leaderstats ~= nil)
				print("[XP DEBUG] Found XP value:", xp ~= nil)

				if xp then
					-- Get base XP values DIRECTLY from stats
					local baseMinXP = stats.XP_Min
					local baseMaxXP = stats.XP_Max

					-- Calculate level bonus with a more aggressive scaling
					local levelMultiplier = 1 + (stats.Level - 1) * 1.0

					-- Calculate final XP amount ensuring higher levels always give more XP
					local finalMinXP = math.floor(baseMinXP * levelMultiplier)
					local finalMaxXP = math.floor(baseMaxXP * levelMultiplier)

					-- Randomly select XP within the new scaled range
					local finalXPAmount = math.random(finalMinXP, finalMaxXP)

					print("[XP CALCULATION DETAILS]")
					print("Enemy Name:", enemy.Name)
					print("Enemy Level:", stats.Level)
					print("Original Base XP Range:", baseMinXP, "-", baseMaxXP)
					print("Level Multiplier:", levelMultiplier)
					print("Final XP Range:", finalMinXP, "-", finalMaxXP)
					print("Final XP Amount:", finalXPAmount)
					print("-------------------")

					xp.Value = xp.Value + finalXPAmount

					StarterGui:SetCore("SendNotification", {
						Title = "XP Gained!",
						Text = "+" .. tostring(finalXPAmount) .. " XP",
						Duration = 3
					})
				end
			end
		end
	end
end

-- Function to search through all subfolders for the "Death" animation
local function findDeathAnimationInFolder(folder)
	local animationsFolder = folder:FindFirstChild("Animations")
	if animationsFolder then
		local deathAnimation = animationsFolder:FindFirstChild("Death")
		if deathAnimation then
			return deathAnimation
		end
	end
	return nil
end

-- Function to play the death animation
local function playDeathAnimation(enemy)
	local humanoid = enemy:FindFirstChildOfClass("Humanoid")
	if humanoid then
		local enemyFolder = ReplicatedStorage:WaitForChild("Enemy Assets"):WaitForChild("Melee"):FindFirstChild(enemy.Name)
		if not enemyFolder then
			enemyFolder = ReplicatedStorage:WaitForChild("Enemy Assets"):WaitForChild("Ranged"):FindFirstChild(enemy.Name) or
				ReplicatedStorage:WaitForChild("Enemy Assets"):WaitForChild("Boss"):FindFirstChild(enemy.Name)
		end

		if enemyFolder then
			local deathAnimation = findDeathAnimationInFolder(enemyFolder)
			if deathAnimation then
				local deathTrack = humanoid:LoadAnimation(deathAnimation)
				deathTrack:Play()
				deathTrack.Stopped:Wait()
			else
				warn("[ERROR] Death animation not found for", enemy.Name)
			end
		else
			warn("[ERROR] Enemy folder not found for", enemy.Name)
		end
	end
end

-- Function to update the health bar for an enemy
local function updateHealthBar(enemy)
	local humanoid = enemy:FindFirstChildOfClass("Humanoid")
	local head = enemy:FindFirstChild("Head")
	local stats = EnemyStatsModule[enemy.Name]

	if humanoid and head and stats then
		local healthGui = head:FindFirstChild("Health")
		if healthGui then
			local mainFrame = healthGui:FindFirstChild("Main")
			local barFrame = mainFrame and mainFrame:FindFirstChild("Bar")
			local nameFrame = mainFrame and mainFrame:FindFirstChild("Name")
			local bgFrame = barFrame and barFrame:FindFirstChild("BG")
			local healthLabel = barFrame and barFrame:FindFirstChild("Health")
			local levelLabel = nameFrame and nameFrame:FindFirstChild("Level")
			local nameLabel = nameFrame and nameFrame:FindFirstChild("Name")

			if bgFrame and healthLabel and levelLabel and nameLabel then
				-- Update health bar size and text
				local healthPercent = humanoid.Health / humanoid.MaxHealth
				bgFrame.Size = UDim2.new(healthPercent, 0, 1, 0)
				healthLabel.Text = string.format("%d/%d", math.floor(humanoid.Health), math.floor(humanoid.MaxHealth))

				-- Update name and level
				nameLabel.Text = enemy.Name
				levelLabel.Text = "Level " .. tostring(stats.Level)

				-- Update health bar color
				if healthPercent > 0.5 then
					bgFrame.BackgroundColor3 = Color3.fromRGB(0, 255, 0)  -- Green
				elseif healthPercent > 0.2 then
					bgFrame.BackgroundColor3 = Color3.fromRGB(255, 255, 0)  -- Yellow
				else
					bgFrame.BackgroundColor3 = Color3.fromRGB(255, 0, 0)  -- Red
				end
			end
		else
			warn("[ERROR] Health GUI not found on:", enemy.Name)
		end
	else
		warn("[ERROR] Humanoid, Head, or Stats missing for:", enemy.Name)
	end
end

-- Function to emit hit particles when health decreases
local function playHitParticles(enemy)
	local humanoidRootPart = enemy:FindFirstChild("HumanoidRootPart")
	if humanoidRootPart then
		local hitParticles = ReplicatedStorage:WaitForChild("Enemy Assets"):WaitForChild("All Enemies"):WaitForChild("Particles"):WaitForChild("Hit"):Clone()
		hitParticles.Parent = humanoidRootPart:FindFirstChild("Particles")
		hitParticles:Emit(1)
	end
end

-- Function to disable collision on death
local function disableCollisionOnDeath(enemy)
	for _, part in ipairs(enemy:GetDescendants()) do
		if part:IsA("BasePart") then
			part.CollisionGroup = "DeadEnemy"
		end
	end
end

-- Function to enable collision (when alive)
local function enableCollisionOnLive(enemy)
	for _, part in ipairs(enemy:GetDescendants()) do
		if part:IsA("BasePart") then
			part.CollisionGroup = "Enemy"
		end
	end
end

-- Function to play death sound
local function playDeathSound(enemy)
	local enemyFolder = ReplicatedStorage:WaitForChild("Enemy Assets"):WaitForChild("Melee"):FindFirstChild(enemy.Name)
	if not enemyFolder then
		enemyFolder = ReplicatedStorage:WaitForChild("Enemy Assets"):WaitForChild("Ranged"):FindFirstChild(enemy.Name) or
			ReplicatedStorage:WaitForChild("Enemy Assets"):WaitForChild("Boss"):FindFirstChild(enemy.Name)
	end

	if enemyFolder then
		local deathSound = enemyFolder:FindFirstChild("Sounds"):FindFirstChild("DeathSound")
		if deathSound then
			deathSound:Play()
		else
			warn("[ERROR] Death sound not found for", enemy.Name)
		end
	else
		warn("[ERROR] Enemy folder not found for", enemy.Name)
	end
end

-- Function to process enemy death
local function processEnemyDeath(enemy)
	local head = enemy:FindFirstChild("Head")
	local humanoidRootPart = enemy:FindFirstChild("HumanoidRootPart")

	if head and humanoidRootPart then
		-- Change appearance
		for _, part in ipairs(enemy:GetDescendants()) do
			if part:IsA("BasePart") and part.Name ~= "Head" then
				part.TextureID = ""
				for _, decal in ipairs(part:GetDescendants()) do
					if decal:IsA("Decal") then
						decal:Destroy()
					end
				end
				part.Material = Enum.Material.Neon
				part.Color = Color3.fromRGB(255, 0, 0)
			end
		end

		-- Play death effects
		local deathParticles = ReplicatedStorage:WaitForChild("Enemy Assets"):WaitForChild("All Enemies"):WaitForChild("Particles"):WaitForChild("Death"):Clone()
		deathParticles.Parent = humanoidRootPart:FindFirstChild("Particles")
		deathParticles:Emit(1)

		task.spawn(function() 
			playDeathAnimation(enemy) 
		end)

		-- Fade out
		wait(0.5)
		local tweenInfo = TweenInfo.new(3, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)

		for _, part in ipairs(enemy:GetDescendants()) do
			if part:IsA("BasePart") then
				local tween = TweenService:Create(part, tweenInfo, {Transparency = 1})
				tween:Play()
			end
		end

		-- Cleanup
		wait(1)
		if head:FindFirstChild("Health") then 
			head:FindFirstChild("Health"):Destroy() 
		end
		if deathParticles then 
			deathParticles:Destroy() 
		end

		wait(2)
		enemy:Destroy()
	else
		warn("[ERROR] Head or HumanoidRootPart not found for:", enemy.Name)
		enemy:Destroy()
	end
end

local function initializeEnemyHealth(enemy)
	-- Early return if already initialized
	if enemy:FindFirstChild("HealthInitialized") then
		return
	end

	local humanoid = enemy:FindFirstChildOfClass("Humanoid")
	local stats = EnemyStatsModule[enemy.Name]

	if humanoid and stats then
		-- Create initialization flag
		local healthInitialized = Instance.new("BoolValue")
		healthInitialized.Name = "HealthInitialized"
		healthInitialized.Value = true
		healthInitialized.Parent = enemy

		-- Create death processed flag
		local deathProcessed = Instance.new("BoolValue")
		deathProcessed.Name = "DeathProcessed"
		deathProcessed.Value = false
		deathProcessed.Parent = enemy

		-- Set up initial health
		humanoid.MaxHealth = stats.Health + (stats.Health * ((stats.Level - 1) * 0.5))
		humanoid.Health = humanoid.MaxHealth
		updateHealthBar(enemy)
		enableCollisionOnLive(enemy)

		-- Declare connections at the top of the scope
		local healthConnection
		local deathConnection

		-- Connect to HealthChanged for health updates only
		healthConnection = humanoid.HealthChanged:Connect(function()
			if humanoid.Health < humanoid.MaxHealth then
				playHitParticles(enemy)
			end
			updateHealthBar(enemy)

			if humanoid.Health < 0 then
				humanoid.Health = 0
			end
		end)

		-- Connect to Died for death handling
		deathConnection = humanoid.Died:Connect(function()
			print("[DEBUG] Died event triggered for:", enemy.Name)

			-- Check if we've already processed this death
			if deathProcessed.Value then return end

			deathProcessed.Value = true
			print("[DEBUG] Processing death for:", enemy.Name)

			-- Disconnect both connections
			healthConnection:Disconnect()
			deathConnection:Disconnect()

			-- Process death
			disableCollisionOnDeath(enemy)
			task.spawn(playDeathSound, enemy)
			awardXPToContributors(enemy)

			-- Process death effects and cleanup
			task.spawn(function()
				processEnemyDeath(enemy)
			end)
		end)
	else
		warn("[ERROR] Failed to initialize health for:", enemy.Name)
	end
end

-- Function to initialize all enemies
local function initializeAllEnemies()
	local enemiesFolder = game.Workspace:WaitForChild("Enemies")

	-- Initialize all current enemies
	for _, enemy in ipairs(enemiesFolder:GetChildren()) do
		if enemy:IsA("Model") then
			task.spawn(function()
				initializeEnemyHealth(enemy)
			end)
		end
	end

	-- Set up connection for new enemies
	enemiesFolder.ChildAdded:Connect(function(enemy)
		if enemy:IsA("Model") then
			task.spawn(function()
				wait()  -- Small delay for enemy to load
				initializeEnemyHealth(enemy)
			end)
		end
	end)
end

-- Start initialization
initializeAllEnemies()

return initializeAllEnemies
3 Likes

The fact that it only happens for enemies within close range of each other indicates to me it could be an issue with the hitbox detection.

What steps did you take to rule that out?

I created a part that damages the enemy. just a simple meshpart with a script in it to deal damage to the enemy. i killed the enemy using the meshpart and they all died

Hi bro!

The problem you mention is quite peculiar. After reading and analyzing the script myself, I believe it could be related to how the death of enemies is being handled in your script. Although you mention that the combat script is not the issue, there may be some unexpected behavior in the logic for enemy deaths that causes all enemies to die when one is eliminated.

In summary, it could be related to shared variables or connections between enemies rather than an explicit error in damage detection or area of impact.

I’ll try to help you resolve the potential points of complication in your code so you can make the necessary adjustments.

First, make sure the logic handling enemy deaths is not affecting all the enemies in the area. For example, if you’re using a loop or a function that impacts all nearby enemies, this could cause all of them to die when one is eliminated.

-- Here, check that you’re not iterating over all the enemies in the area
for _, enemy in ipairs(enemiesFolder:GetChildren()) do
    if enemy:IsA("Model") and enemy:FindFirstChildOfClass("Humanoid") then
        -- Also ensure you’re only affecting the specific enemy
        if enemy == specificEnemy then
            -- Death logic
        end
    end
end

Second, carefully review the function you have called processEnemyDeath to ensure it’s not affecting all enemies in the area. Ensure you’re only manipulating the specific enemy that died.

local function processEnemyDeath(enemy)
    -- Ensure you’re only affecting the specific enemy
    if enemy and enemy:FindFirstChild("Head") and enemy:FindFirstChild("HumanoidRootPart") then
        -- Death logic
    end
end

Make sure that variables and values like DeathProcessed or HealthInitialized are unique to each enemy. If these variables are not unique (e.g., they are shared across enemy instances), a change in one enemy could affect all of them.

Now, if this doesn’t solve the problem, I recommend trying other methods, such as checking any part of the code where you use iterations like :GetChildren() or :GetDescendants() to ensure that changes only affect the specific enemy.

You could also try removing parts of the code, such as particle emission, animation processing, or background tasks (task.spawn), to identify which part causes the error (go step by step). Additionally, I recommend adding validation in the Died event to ensure only enemies with Health <= 0 are processed.

Add more specific debug messages as well. In the humanoid.Died event, try printing the identity of the enemy (enemy.Name) and the state of DeathProcessed. In initializeEnemyHealth, confirm that each enemy has unique variables (HealthInitialized).

I hope some of my key points help you solve the problem. :blush:

2 Likes

Thank you! I will try your suggestions and tell you how it goes.

1 Like

The issue was the terrain. Incredible. for some reason certain parts of my terrain are causing the issue. specifically the uneven parts of my terrain.