GetPartsInPart Bug

I made a hurtbox with GetPartsInPart which is supposed to hurt another player but I keep getting an error at line 10 every time for this

local Players = game:GetService("Players")
local timealive = script.Parent.TimeAlive.Value
local damage = script.Parent.Damage.Value

while task.wait(timealive) do
	local deb = false
	local objectsInSpace = workspace:GetPartsInPart(script.Parent)
	for i, v in pairs(objectsInSpace) do
		if not v:IsA("BasePart") then return end
		if v.Parent:FindFirstChild("Humanoid") then
		if deb == true then continue end
			deb = true
			local player = Players:GetPlayerFromCharacter(v.Parent)
			if player.Name == script.Parent.SpawnedBy.Value then
				print("same person!")
			else
				player.Character:WaitForChild("Humanoid").Health = player.Character:WaitForChild("Humanoid").Health - damage
				player.Character:FindFirstChild("Attributes"):FindFirstChild("Stunned").Value = true
				task.wait(timealive / 2)
				player.Character:FindFirstChild("Attributes"):FindFirstChild("Stunned").Value = false
							
			end		
		end
	end
end

I’ve tried multiple things to fix it but the glitch keeps happening. What’s weirder is that one player will always have it working and one player will always have it broken.

To achieve precision, it must be run at every step.
Debounce must be per player. This is possibly the reason why it works for one player but not for another.
And there are other problematic issues.

local Players = game:GetService("Players")
local timealive = script.Parent.TimeAlive.Value
local damage = script.Parent.Damage.Value

local playerDebounces = {}

game:GetService("RunService").Heartbeat:Connect(function()
    local objectsInSpace = workspace:GetPartsInPart(script.Parent)
    for i, v in pairs(objectsInSpace) do
        if not v:IsA("BasePart") then continue end -- `continue` to keep processing other parts
        local character = v.Parent
        local humanoid = character:FindFirstChildOfClass("Humanoid")
        
        if humanoid then
            local player = Players:GetPlayerFromCharacter(character)
            
            if player then

                if player.Name == script.Parent.SpawnedBy.Value then
                    print("same person!")
                    continue
                end

                if playerDebounces[player.UserId] then
                    continue
                end
                
                playerDebounces[player.UserId] = true
                
                humanoid.Health = humanoid.Health - damage
                
                local attributesFolder = character:FindFirstChild("Attributes")
                if attributesFolder then
                    local stunnedAttribute = attributesFolder:FindFirstChild("Stunned")
                    if stunnedAttribute then
                        stunnedAttribute.Value = true
                        task.spawn(function()  -- to avoid blocking the current thread
                            task.wait(timealive / 2)
                            if stunnedAttribute and stunnedAttribute.Parent then -- check if the attribute still exists
                                stunnedAttribute.Value = false
                            end
                            playerDebounces[player.UserId] = nil
                        end)
                    else
                        playerDebounces[player.UserId] = nil
                    end
                else
                    playerDebounces[player.UserId] = nil
                end
            end        
        end
    end
end)

Used Heartbeat because I assumed it is server-side. I think it would work better if it were client-side.

This code is not exactly what you need, but rather an idea of how it should be. On one hand, your current code isn’t very suitable for the task at hand. On the other hand, I don’t have enough information about how your game works, so I can’t provide the exact code that would solve your problem.

I see. It goes from a client side to a module script to a server script i believe, although i havent looked in a bit. Ill try to make the debounce client side so only plrs have cd instead of everyone. If that doesnt work i will try your solution.

How does a module script change any context?
That’s a very odd way of thinking you all beginners have.
Ideally it should run in parallel.

Its the thing that clones the hurtbox and sets it at the player. I do it so I can run it at anytime like if someone like presses E or something i can activate the hurtbox. I would check exactly how it works but im out rn so I cant.

I would not make the cooldown client-sided as an exploiter could bypass it. I suggest keeping it server-sided.

As @lumizk shared in their code, they provided a flexible way for players to each have their own cooldown. The trick is to keep track of the players within it and then remove them afterwards.

local IndividualCoolDowns = {} -- A table referencing players currently on cooldown. Think of it like a grocery list, but rather then vegetables it's players. Okay that sounds weird uhhh moving along now.
local CoolDownTime = 5 -- How long each player's in cooldown for.

local function CoolDownPlayer(nm)
    IndividualCoolDowns[nm] = true -- Add their name to the list.
    delay(CoolDownTime, function() -- After a delay...
        IndividualCoolDowns[nm] = nil -- Remove them from the list. Setting their reference to nil mitigates potential memory leaks.
    end)
end

local function IsPlayerInCooldown(nm)
    return IndividualCoolDowns[nm] ~= nil -- Returns truthy or falsey if the player's name's present in the list.
    -- It's good practice in my opinion to check against the exact data type and not always resort to "not". I can expand upon this if inquired about it further.
end

Part.Touched:Connect(function(hit)
    local plyr = game.Players:GetPlayerFromCharacter(hit.Parent) -- Grab the player if it's their character
    if plyr ~= nil and not IsPlayerInCoolDown(plyr.Name) then -- Was it a player and are they not already in the list?
        CoolDownPlayer(plyr.Name) -- Run the function to add them.
        -- Additional code logic.
    end
end)

It keeps things evened out and makes sure everybody – not just one player – can interact with XYZ thing. =)

EDIT
I should also note that this can be used in ModuleScripts too.

Replace the end section with this:

local function AttachTouchedLogic(prt)
    if not prt:IsA("BasePart") then
        return
    end
    prt.Touched:Connect(function(hit)
        local plyr = game.Players:GetPlayerFromCharacter(hit.Parent) -- Grab the player if it's their character
        if plyr ~= nil and not IsPlayerInCoolDown(plyr.Name) then -- Was it a player and are they not already in the list?
            CoolDownPlayer(plyr.Name) -- Run the function to add them.
            -- Additional code logic.
        end
    end)
end

return AttachTouchedLogic

Call require on that ModuleScript and it’ll do just that trick. :+1:

Alright I’m at my computer and how it works is that a local script gets a mouse1 or mobile input then goes to a server script to then activate the module script which spawns the hurtbox where it needs to be. Then that hurtbox has the script I have posted in it. I don’t think you will need to rework anything but just incase you needed to know! Also your comment about cooldowns being hackable if client side was helpful because I had recently change other scripts in my game to do that!

Edit: The first response, which conveniently is also the first one i tried worked. The only change I made was:

game:GetService("RunService").Heartbeat:Connect(function()

end)

to

while task.wait(timealive) do

end

I wouldn’t recommend doing while wait(n) do as it’s relying on the fact that task.wait and wait return a truthy value. I would suggest instead making the condition a boolean and then running the sleeper function. Since RunService.Heartbeat runs after physics had concluded that cycle that makes it a better method to use.
RunService | Documentation - Roblox Creator Hub
If the issue’s that there needs to be some time between each time it execute the code logic, this can be mitigated with Heartbeat passing the delta time (the amount of time between each cycle)

local TimeTracker = 0
RunService.Heartbeat:Connect(function(DeltaTime)
	TimeTracker = TimeTracker + DeltaTime
	local RoundedTime = math.floor(TimeTracker / timealive)
	if RoundedTime >= 1 then
		for i = 1, RoundedTime do
			-- code logic
		end
		TimeTracker = TimeTracker - (RoundedTime * timealive)
	end
end)

This would wait an X amount of time before executing the code’s instructions and run it the amount of times needed if Heartbeat took unexpectedly longer than usual to fire.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.