Unable to break loop + Touched Event isn't working

Hi! I am working on my first boss battle, and I have two problems. But it would be nice if someone could even solve one of both :slight_smile:

The main code snippet with the problems is this:

for _,Part in ipairs(Golem:GetDescendants()) do --For each part in the Golem body
	if Part:IsA("BasePart") then --Filter out the baseparts from other things
		local Event = Part.Touched:Connect(function(hitpart) --If some part touches something
			print("Part was touched")
			if hitpart.Parent:FindFirstChild("Humanoid") then --Control if the hitted Instance has a humanoid object
				print("Humanoid found")
				hitpart.Parent.Humanoid:TakeDamage(90) --If yes, it's a player and damage it
				Humanoid.WalkSpeed = 16
				print("End of the charge")
				return -- <<--HERE I want to break
			end
		end)
		table.insert(Events, #Events+1, Event)
	end
end
First Problem

I my loop (watch above). Now, I know how to break basics loops, and I thought this would be the same case as all my others loops, but if I put a break keyword there, the script editor tells me this error: syntax error: break satement must be inside a loop.But, from that what I learned, this IS a loop, a generic for loop (correct me if I am wrong). So, what’s going wrong there?

Second Problem

Now, if I try this script (without trying to break it), at the charging, my Boss will not damage me, but I can’t find out why. I will try to explain it:

  1. It should, for each part of the body of the boss, make an event for every time that this part is touched
  2. If it finds something that has a humanoid inside (our player), make the boss damage it and he walk normally (The boss is very fast while he charges, this is why I make him go slower)
    It should be easy, but there are my problems:
  3. The boss still walks fast
  4. The boss dosen’t do damage to the player

I tried to debug it using the print function and the Lua debugger, but (again, this “but”):

  1. It dosen’t print anything
  2. The Lua debugger dosen’t let me see what happens inside the event

At result: I can’t inspect my own code!

At this point I really don’t know what else to do, I tried everything what I could. For my first problem I tried to search how to break a for in pairs loop, but I found nothing. For my second problem I don’t even know what’s happening, so I can’t research. I am stuck here, and I can’t continue without finish this part. So, even if you only know one solution, please tell it me

Thanks for reading!
~~Eterna

2 Likes

Ok so for the first problem with the break the issue is the placement of the return which is inside the scope of the event function which is an event connection and not under the loop hence the error.

Fixed return statement placement
	if Part:IsA("BasePart") then --Filter out the baseparts from other things
		local Event = Part.Touched:Connect(function(hitpart) --If some part touches something
			print("Part was touched")
			if hitpart.Parent:FindFirstChild("Humanoid") then --Control if the hitted Instance has a humanoid object
				print("Humanoid found")
				hitpart.Parent.Humanoid:TakeDamage(90) --If yes, it's a player and damage it
				Humanoid.WalkSpeed = 16
				print("End of the charge")
				--Return was here
			end
		end)
				return -- <<-- now its move here under the loop
		table.insert(Events, #Events+1, Event)
	end

However, even if you break the loop then it the loop will only affect one part so why do it?

The second problem, it’s probably related to how you are setting up the connection. Looking at the code snippet by itself it seems to follow normal working kill brick logic so it must be something else. I believe it’s because of this:

Golem:GetDescendants()

Are you sure you are setting the connection on the correct golem? Are you perhaps cloning it somewhere? This might be a problem if the script is within the ServerScriptService that manages all the models? Anyway yeah, all I can do is guess seems like there is more to the code than just the kill brick connection code. More information will be required like how are you handling the event disconnecting within the table you got and such.

1 Like

I thought it would break the whole loop. And I am doing this because I want that the loop stop when the golem touch the player, wich then would damage the player and the loop wouldn‘t be necessary anymore. But, ok, I am going to try this out

What do you mean? I am saving the event connection to then, later, once the loop finshed, deconnect all these events to avoid lag

Oh… I didn‘t thought this in this way… ok, but yes, you‘re right, so I am going to try the collection service and see if it can solve it. But the first, I am not sure, but a try is always welcome (but with the collection service I won‘t need it I think)

Oh so that’s the issue you shouldn’t disconnect right after setting the connections, you should only disconnect once the “Charge” is complete which I’m not sure what type of charge. If you immediately disconnect it then the event function won’t trigger at all as it’s disconnected hence no print statements when you touch the golem.

I think you are confusing loops and events as events are an entirely different thing. If you want the connection to fire in your case once you can just disconnect it like so:

		local Event = Part.Touched:Connect(function(hitpart) --If some part touches something
			print("Part was touched")
			if hitpart.Parent:FindFirstChild("Humanoid") then --Control if the hitted Instance has a humanoid object
				print("Humanoid found")
				hitpart.Parent.Humanoid:TakeDamage(90) --If yes, it's a player and damage it
				Humanoid.WalkSpeed = 16
				print("End of the charge")
				--Disconnect all the events in the kill brick table
				for i,Event in pairs(Events) do
					if Event then
						Event:Disconnect()
					end
				end
			end
		end)

Hmm, if you want to optimize it further you can instead use a can collide false part welded onto the model to act a the hitbox as this method of detecting collisions for a model may not be scalable if the model has a lot of parts as it’ll generate a lot of events and eat up memory. Collection services will not necessarily be needed here if you use the above method.

Okay based on this reply I don’t think you understand what the loop is actually doing.

As I understand you just want the boss to deal one attack and then stop which you think can be done by breaking the loop. All you actually need to do is disconnect the events as that is what will cause the boss to deal damage. The only point of the loop to to create the touched events in the first place and then it stops automatically.

1 Like

Ok, I changed a large part of my script and implemented the CollectionService. I think I fixed one little part, because now it starts printing. But, there are still problems that I think that would disappear once I am able to break a for in pairs loop. This is what I got now (my whole module script):

local CollectionService = game:GetService("CollectionService")
local ShouldDisconnectCharge = false

local Attacks = {}

function Attacks.init(Golem) --Init all the whole system
	for _,part in ipairs(Golem:GetDescendants()) do --For each part in the Golem model
		if part:IsA("BasePart") then --If it's a true part
			if not CollectionService:HasTag(part, "Golem Killpart") then --If it's not tagged
				CollectionService:AddTag(part, "Golem Killpart") --Tag it (it will be used for some attacks like charging)
			end
		end
	end
end

function Attacks.Charge(Golem, TargetPosition) --This is the charge attack of the Golem
	local HRP = Golem.HumanoidRootPart
	local Humanoid = Golem.Humanoid
	local Events = {} --Store the events to later delete them

	HRP.Anchored = true --Block the Golem from moving

	local x,z = (TargetPosition - HRP.Position).X, (TargetPosition - HRP.Position).Z --We are getting the X and Z components, we want to define ourself the y component
	local ray = Ray.new(HRP.Position, Vector3.new(x, HRP.Position.Y, z).Unit * 10000) --Create a ray between the boss and the target point
	local hit,position = workspace:FindPartOnRayWithIgnoreList(ray, Golem:GetDescendants()) --Get the hitpoint position of the ray: Find with this the destination of the charge

	Humanoid.WalkSpeed = 40

	HRP.Anchored = false

	Humanoid:MoveTo(position) --Move the Golem to the found position

	for _,Part in ipairs(CollectionService:GetTagged("Golem Killpart")) do
		spawn(function()
			local Event = Part.Touched:Connect(function(hitpart) --If some part touches something
				if not ShouldDisconnectCharge then --If the player wasn't attacked (Else it will spam attack the player)
					print("Attack")
					if hitpart.Parent:FindFirstChild("Humanoid") then --Control if the hitted Instance has a humanoid object
						hitpart.Parent.Humanoid:TakeDamage(90) --If yes, it's a player and damage it
						repeat
							Humanoid.WalkSpeed = 16
						until
						Humanoid.WalkSpeed == 16
						print(Humanoid.WalkSpeed)
						ShouldDisconnectCharge = true
					end
				end
			end)
			table.insert(Events, #Events+1, Event)
		end)
	end

	if ShouldDisconnectCharge then
		for _,event in ipairs(Events) do
			print("Event Disconnected")
			event:Disconnect()
		end
		ShouldDisconnectCharge = false
	end

end

return Attacks

I just saw your reply. I tried this but there are things that I want to avoid:

  1. My loop create events, yes, but once it do the damage I want to stop the loop. Imagine you have a boss with over 50 parts, once one part did the damage, the loop still will go for 49 parts, creating and destroying events over and over, wich would create lag
  2. Bugs. Right now, I have 2 bugs, where the boss, after he did the damage, dosen’t lower his speed and the boss do over 100 damage instead of 25 (he do 2x25, then this 90). I think the second is do because the loop dosen’t stop and for all parts in the Boss model the script does damage, wich, again, isn’t what I want

I really hope there’s a way to break this kind of loops or atleast a alternative way. But thanks for trying to help me!

1 Like

First Problem: You’re in a function, not a loop - anything within local Event = Part.Touched:Connect(function(hitpart) and before the relevant end) is a function and not a loop.

local maidClass = Maid.new()
--[[
Alternative to table.insert assuming I'm right in thinking what you're trying to do.
The Maid Module is an awesome way of removing connections, objects etc - anything you want to clean up!

To remove everything in the maid it's maidClass:DoCleaning() or maidClass:Destroy()
--]]

local function GolemnHit(hitPart)
    if not hitPart.Parent:FindFirstChild("Humanoid") then return end
    --We use "return" as despite being started by a for loop, we're now in a function (not a loop - so 'break' doesn't work)
	hitPart.Parent.Humanoid:TakeDamage(90)
	Humanoid.WalkSpeed = 16
	
    --[["return -- <<--HERE I want to break"
    return works exactly the same as break, but for functions. The reason it doesn't work in this case is because your function still gets called when something touches it. 
    To fix this we need to disconnect your event which "calls" this function. We can use the maid we made earlier for this.
    --]]
    maidClass:DoCleaning() --this will destroy all the connections which means no .Touched will work after damage is applied.
    --If you want it to stop damage from that one golemn then I can show you how to do that.
end

for _,Part in ipairs(Golem:GetDescendants()) do 
    if not Part:IsA("BasePart") then return end --cleaned it up for u
    maidClass:GiveTask(Part.Touched:Connect(GolemnHit))
end

I think the bit which confused you is th different between, break, return and connections. Here’s a summary:

  • break - stops the loop from continuing, equivelant to return
  • returm - stops the function from continuing, equivelant to break
  • connection - an event that gets called when the relevant change is made to fire it, in your case two objects intersecting. To break a connection you can :Disconnect() it and then set it to nil.
    Documentation - Roblox Creator Hub

Hope this is of some help! Oh and maids can be found here:

Once the ModuleScript is placed in you can just set it up like:

local Maid = require(Path.To.Maid)
local maidClass = Maid.new()
2 Likes

Okay so to break the loop as you’d like to you can do something like this:

local hasAttacked = false
for _,Part in ipairs(Golem:GetDescendants()) do 
    if hasAttacked then 
        break 
    end
	if Part:IsA("BasePart") then 
		local Event = Part.Touched:Connect(function(hitpart) 
			print("Part was touched")
			if hitpart.Parent:FindFirstChild("Humanoid") then 
				print("Humanoid found")
				hitpart.Parent.Humanoid:TakeDamage(90)
                hasAttacked = true -- This will break the loop on the next cycle
				Humanoid.WalkSpeed = 16
				print("End of the charge")
			end
		end)
		table.insert(Events, #Events+1, Event)
	end
end

It is worth saying though that there is decent chance that the loop will always just complete itself before anyone is touched by it.

Edit: You can merge this idea of having a “hasAttacked” variable with the code presented by @jeditcdisback if you want to try and reduce making unnecessary connections.

1 Like

I solved it, instead of struggling with events I just used the while loop. I am writing from mobile, but I will try to remember what I wrote:

local StoppedAttacking = false
local Event

while not StoppedAttacking do --If the attack didn‘t stop
    if distance_is_close_enough then --If the distance is close enough, then go below (I just forgot what kind of Math I used for this…)
        PlayerHumanoid:TakeDamage(90) --Damage the player with 90 hitpoints
        BossHumanoid:MoveTo(BossHumanoidRootPart.Position) --Stop the Boss from running
        break
    end
    Event = BossHumanoid.MoveToFinished:Connect(function()
        StoppedAttacking = true
    end)
end

Event:Disconnect()

But, still a huge thanks for everyone who helped me! (I am going to mark @jeditcdisback‘s reply because iti gave me the idea witht he debounce, with this hasAttacked)