[Solved] Help with grenade damage function

Hey all! I’m working on a grenade, and I’ve run into an issue that needs to be resolved.

The grenade works and damages the player, but since I’m dealing damage using the explosions hit function it does way more damage than is intended, or doesn’t damage all players.

I added a debounce, but that made it so that only 1 person can be damaged by the grenade at a time. I added a boolean value to each player that determines whether they’ve been damaged, and if it is false they take damage from the grenade which sets the value to true.

		explosion.Hit:Connect(function(hit,distance)


			local hum = hit.Parent:FindFirstChildOfClass("Humanoid")
			local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
			
			if player then
				print(player.Name.." Was hit by an explosion.")
				if hum then
					local damaged = player.isDamaged.Damaged
					if damaged.Value == false then
						print("damaged the player")
						hum:TakeDamage((1 - distance / explosion.BlastRadius) * (maxDmg - minDmg) + minDmg)
						wait(0.1)
						damaged.Value = true
						wait(1)
					end
					wait(3)
					damaged.Value = false
				end

			end		

		end)

This is all wrapped inside a coroutine that creates an explosion that doesn’t destroy joints.

I need all of the players to take damage, but not be instantly killed by the explosion because the hit function triggers multiple times.

This is a server script.

Edit: I believe I got it working, all I had to do was move where I set my damaged boolean to true to above where the player first takes damage to make it more like a debounce.

edit: nvm you already did something similar to this

I’ve recently ran into this. What you do is set BlastPressure to 0. Then with the explosion, you use the Touch event. Every player that get’s touched by the explosion you apply the damage to. The event will fire once for each player that was hit by it.

One interesting thing is that you can vary the percentage of the blast damage based on the distance the player is from the explosion. Since you have the coordinates of the explosion, the blast radius, and the player’s primary part CFrame, you can calculate the distance like this:

distance = math.sqrt((x1 - x2)^2 + (y1 - y2)^2 + (z1 - z2)^2)

Or, more programatically,

distance = math.sqrt(
	(x1 - x2) * (x1 - x2) +
	(y1 - y2) * (y1 - y2) +
	(z1 - z2) * (z1 - z2) )

Then you can use the following code to apply damage:

human:TakeDamage(damage * (distance / 20)) with 20 being the blast radius.

1 Like

Hey, thanks for the quick reply. Unfortunately it seems that setting BlastForce (I’m assuming you mean BlastPressure) to 0 doesn’t cause the event to run just once per player. Because the player is touched by the explosion multiple times it runs multiple times dealing far too much damage than is input. Thanks for the math to calculate distance I’m sure it will be useful.

I went back and looked at the rocket script from Roblox’s Rocket Launcher that’s available in the Toolbox and they too are using a table to keep track of which players took damage and which didn’t. The asset ID for their tool is 47637.

why don’t you use a circle mesh similar to size of the explosion and set Transparency to 1 and clone it to the position of the grenade and inside it have a Touched event

Local Part = script.Parent
Part.Touched:Connect(function(hit)
local Player = game.Players:GetPlayerFromCharacter(hit.Parent)
if Player then
local DamageToTake = ((Player.HumanoidRootPart.Position - Part.Position) * (maxDmg - minDmg) + minDmg)
Player:FindFirstChildWhichIsA("Humanoid"):TakeDamage(DamageToTake)
end
end)

An easy fix is to change the script from checking each individual part of the character to checking to see if the hit part name is HumanoidRootPart. Since every character only has one it will only fire once for each player.

Don’t know if I did something wrong, but the player is still being hit multiple times despite it only checking for if the HRP was hit.

		explosion.Hit:Connect(function(hit,distance)


			local hrp = hit.Parent:WaitForChild("HumanoidRootPart")
			local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
			
			if player then
				print(player.Name.." Was hit by an explosion.")
				if hrp then
					local hum = hrp.Parent:FindFirstChildOfClass("Humanoid")
					local damaged = player.isDamaged.Damaged
					if damaged.Value == false then
						print("damaged the player")
						hum:TakeDamage((1 - distance / explosion.BlastRadius) * (maxDmg - minDmg) + minDmg)
						wait(0.1)
						damaged.Value = true
						wait(1)
					end
					wait(3)
					damaged.Value = false
				end

			end		

		end)

Ignore my previous reply. Here’s how to fix it. You have to attach a script to the grenade (aka projectile) and then enable it so it will execute. When the grenade explodes, you have to reparent the script back to the tool instance (I’m assuming that you are using a grenade launcher) so it will keep executing. Then instead of placing a flag in the Player (AFAIK, that one is replicated), just use a table in the script. The table is only in that script. Here’s the code of the Rocket script from Roblox’s Rocket Launcher…

-----------------
--| Constants |--
-----------------

local BLAST_RADIUS = 8 -- Blast radius of the explosion
local BLAST_DAMAGE = 60 -- Amount of damage done to players
local BLAST_FORCE = 1000 -- Amount of force applied to parts

local IGNORE_LIST = {rocket = 1, handle = 1, effect = 1, water = 1} -- Rocket will fly through things named these
--NOTE: Keys must be lowercase, values must evaluate to true

-----------------
--| Variables |--
-----------------

local DebrisService = game:GetService('Debris')
local PlayersService = game:GetService('Players')

local Rocket = script.Parent

local CreatorTag = Rocket:WaitForChild('creator')
local SwooshSound = Rocket:WaitForChild('Swoosh')

-----------------
--| Functions |--
-----------------

-- Removes any old creator tags and applies a new one to the target
local function ApplyTags(target)
	while target:FindFirstChild('creator') do
		target.creator:Destroy()
	end

	local creatorTagClone = CreatorTag:Clone()
	DebrisService:AddItem(creatorTagClone, 1.5)
	creatorTagClone.Parent = target
end

-- Returns the ancestor that contains a Humanoid, if it exists
local function FindCharacterAncestor(subject)
	if subject and subject ~= workspace then
		local humanoid = subject:FindFirstChildOfClass('Humanoid')
		if humanoid then
			return subject, humanoid
		else
			return FindCharacterAncestor(subject.Parent)
		end
	end
	return nil
end

local function IsInTable(Table,Value)
	for _,v in pairs(Table) do
		if v == Value then
			return true
		end
	end
	return false
end

-- Customized explosive effect that doesn't affect teammates and only breaks joints on dead parts
local TaggedHumanoids = {}  -- **** <<<<<  Damage Table
local function OnExplosionHit(hitPart, hitDistance, blastCenter)
	if hitPart and hitDistance then
		local character, humanoid = FindCharacterAncestor(hitPart.Parent)

		if character then
			local myPlayer = CreatorTag.Value
			if myPlayer and not myPlayer.Neutral then -- Ignore friendlies caught in the blast
				local player = PlayersService:GetPlayerFromCharacter(character)
				if player and player ~= myPlayer and player.TeamColor == Rocket.BrickColor then
					return
				end
			end
		end

		if humanoid and humanoid.Health > 0 then -- Humanoids are tagged and damaged
			if not IsInTable(TaggedHumanoids,humanoid) then
				print("Tagged")
				table.insert(TaggedHumanoids,humanoid)
				ApplyTags(humanoid)
				humanoid:TakeDamage(BLAST_DAMAGE)
			end
		else -- Loose parts and dead parts are blasted
			if hitPart.Name ~= 'Handle' then
				hitPart:BreakJoints()
				local blastForce = Instance.new('BodyForce', hitPart) --NOTE: We will multiply by mass so bigger parts get blasted more
				blastForce.Force = (hitPart.Position - blastCenter).unit * BLAST_FORCE * hitPart:GetMass()
				DebrisService:AddItem(blastForce, 0.1)
			end
		end
	end
end

local function OnTouched(otherPart)
	if Rocket and otherPart then
		-- Fly through anything in the ignore list
		if IGNORE_LIST[string.lower(otherPart.Name)] then
			return
		end

		local myPlayer = CreatorTag.Value
		if myPlayer then
			-- Fly through the creator
			if myPlayer.Character and myPlayer.Character:IsAncestorOf(otherPart) then
				return
			end

			 -- Fly through friendlies
			if not myPlayer.Neutral then
				local character = FindCharacterAncestor(otherPart.Parent)
				local player = PlayersService:GetPlayerFromCharacter(character)
				if player and player ~= myPlayer and player.TeamColor == Rocket.BrickColor then
					return
				end
			end
		end

		-- Fly through terrain water
		if otherPart == workspace.Terrain then
			--NOTE: If the rocket is large, then the simplifications made here will cause it to fly through terrain in some cases
			local frontOfRocket = Rocket.Position + (Rocket.CFrame.lookVector * (Rocket.Size.Z / 2))
			local cellLocation = workspace.Terrain:WorldToCellPreferSolid(frontOfRocket)
			local cellMaterial = workspace.Terrain:GetCell(cellLocation.X, cellLocation.Y, cellLocation.Z)
			if cellMaterial == Enum.CellMaterial.Water or cellMaterial == Enum.CellMaterial.Empty then
				return
			end
		end

		-- Create the explosion
		local explosion = Instance.new('Explosion')
		explosion.BlastPressure = 0 -- Completely safe explosion
		explosion.BlastRadius = BLAST_RADIUS
		explosion.ExplosionType = Enum.ExplosionType.NoCraters
		explosion.Position = Rocket.Position
		explosion.Parent = workspace

		-- Connect custom logic for the explosion
		explosion.Hit:Connect(function(hitPart, hitDistance) OnExplosionHit(hitPart, hitDistance, explosion.Position) end)

		-- Move this script and the creator tag (so our custom logic can execute), then destroy the rocket
		script.Parent = explosion
		CreatorTag.Parent = script
		Rocket:Destroy()
	end
end

--------------------
--| Script Logic |--
--------------------

SwooshSound:Play()

Rocket.Touched:Connect(OnTouched)

I don’t really understand why Roblox sanctioned weapons are using tags. Maybe I’ll ask that in a separate question.

Thanks for the reply, I ended up fixing the issue using the Damaged boolean all I had to do was move it to the top of the script to work more like a debounce. Not having it at the top was dumb on my part to begin with. I would use a table, but I can use the damaged boolean for other things aswell with minimal work.

2 Likes

Hey, just a heads up. Vector3 and Vector2 objects have a built in property that describes their magnitude (see links.) A quicker and tidier way to get the distance between two points in a 2D or 3D space would be to subtract the vectors by one another and then fetching the magnitude of the difference.

Example:

local Point1 = Vector3.new(1,10,4)
local Point2 = Vector3.new(12,48,1)

print((Point1-Point2).Magnitude) -- ~39.673