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