How to stop explosions killing teammates in Roblox

A default Explosion kills every unprotected character in range, through walls. How to set DestroyJointRadiusPercent to 0 and apply your own damage in Hit.

warningLuaudit check: explosions-ignore-rules

What it looks like

A rocket, bomb or ability creates an Explosion with its default settings and lets the engine do the damage. By default an explosion breaks the joints of every character in its radius, killing them outright, whatever team, duel or safe zone they're in, and walls don't block it.

Luau
local explosion = Instance.new("Explosion")
explosion.Position = hitPosition
explosion.Parent = workspace

How to detect it

  • Find every Instance.new("Explosion") and every cloned explosion in server code, and what triggers it.
  • Check DestroyJointRadiusPercent and BlastPressure. At their defaults, every character inside BlastRadius that isn't protected by a ForceField dies, because the explosion breaks the character's neck joint. Joints are only broken while BlastPressure is above 0. Team checks, friendly-fire rules, duel opponents, safe zones and damage amounts the game uses elsewhere don't apply.
  • Roblox's docs say obstacles don't shield anything from an explosion, so parts behind walls are hit too. A blast near a wall reaches players on the other side.
  • The shape Roblox recommends is DestroyJointRadiusPercent = 0 and a Hit handler that applies the game's own rules. A team check inside Hit doesn't help while the explosion still breaks joints itself. Check that handler groups parts by character, as Hit lists from the client taken as they are describes, since Hit fires once per part.
  • If the explosion's position comes from the client, judge that with Hits the server doesn't check were possible too.

What an exploiter could do

Explosive weapons kill teammates, players in safe zones and players outside a duel, in one hit, through walls. With any way to trigger them faster or from further away than intended, an exploiter can clear whole areas, including spawns behind cover.

How to fix it

  • Set DestroyJointRadiusPercent to 0 on every explosion that players can cause.
  • Apply damage in Explosion.Hit, once per character, using the same checks as other weapons: not the attacker's team, inside the duel, outside safe zones, and Humanoid:TakeDamage so ForceFields still protect.
  • If walls should block the blast, raycast from the explosion to each character before damaging them.

Vulnerable example

Vulnerable
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

local cooldown = 4
local throwRange = 60
local lastThrow: { [Player]: number } = {}

ReplicatedStorage.Remotes.ThrowGrenade.OnServerEvent:Connect(function(player: Player, target: unknown)
	local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
	if typeof(target) ~= "Vector3" or not root or not root:IsA("BasePart") then
		return
	end
	if not ((target - root.Position).Magnitude <= throwRange) then
		return
	end
	local now = os.clock()
	if now - (lastThrow[player] or 0) < cooldown then
		return
	end
	lastThrow[player] = now
	local explosion = Instance.new("Explosion")
	explosion.Position = target
	explosion.BlastRadius = 10
	explosion.Parent = Workspace
end)

Players.PlayerRemoving:Connect(function(player)
	lastThrow[player] = nil
end)

Fixed example

Fixed
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

local cooldown = 4
local throwRange = 60
local damage = 60
local lastThrow: { [Player]: number } = {}

ReplicatedStorage.Remotes.ThrowGrenade.OnServerEvent:Connect(function(player: Player, target: unknown)
	local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
	if typeof(target) ~= "Vector3" or not root or not root:IsA("BasePart") then
		return
	end
	if not ((target - root.Position).Magnitude <= throwRange) then
		return
	end
	local now = os.clock()
	if now - (lastThrow[player] or 0) < cooldown then
		return
	end
	lastThrow[player] = now
	local hit: { [Humanoid]: boolean } = {}
	local explosion = Instance.new("Explosion")
	explosion.Position = target
	explosion.BlastRadius = 10
	explosion.BlastPressure = 0
	explosion.DestroyJointRadiusPercent = 0
	explosion.Hit:Connect(function(part)
		local character = part.Parent
		local humanoid = character and character:FindFirstChildOfClass("Humanoid")
		local victim = character and Players:GetPlayerFromCharacter(character)
		if not humanoid or hit[humanoid] or not victim or victim == player then
			return
		end
		hit[humanoid] = true
		if victim.Team == nil or victim.Team ~= player.Team then
			humanoid:TakeDamage(damage)
		end
	end)
	explosion.Parent = Workspace
end)

Players.PlayerRemoving:Connect(function(player)
	lastThrow[player] = nil
end)

Common false positives

  • Explosions that only break scenery, such as destructible walls, with no players allowed nearby, don't touch players.
  • In a free-for-all game with no teams, duels or safe zones, an instant kill may be the design. Report it only if the game's other weapons deal set amounts of damage.
  • Explosions created with DestroyJointRadiusPercent = 0 and damage applied in Hit with the game's own checks, like the fixed example, are fine.