How to debounce Touch events client side?

I’m working on refactoring the swords in SFOTH.

Basically they are all parts that have a server side script subscribed to a Touch event. When the event fires, the server does a bunch of logic to see if the touching part belongs to a player and, if so, damages them.

I’ve found through testing that this event is very chatty. Swinging a sword at a test dummy locally typically generates 15-20 Touch events in a row. My understanding is online this would look like this:

  1. Local player (handling collision) fires 15-20 Touch events to the server
  2. These are guaranteed delivery (guaranteed order?) updates, so very heavy
  3. Server does damage logic 15-20 times

I know how to change my scripts to prevent #3. How do I stop #1 from happening, before these events generate network traffic spam? I really am only interested in TouchStarted. There is a TouchEnded event in the docs but no TouchStarted.

Can I write a LocalScript that filters outgoing Touch events? How? (Not like, logically how, I mean, where do I hook it in?)

16 Likes

To prevent the “chatty” behavior you are describing, I’d use a debounce to only allow one part per click. Once a part inside of a character is detected, use TouchedEvent:Disconnect() and reconnect it once they’re allowed to slash their sword again.

3 Likes

I think by the time the “disconnect” (hey client, it’s cool stop sending events) gets back to the client, the client will have already fired the 15-20 Touched events in my case.

I assume the client is generating Touch events at 30 or 60 Hz. Feels like 60.

5 Likes

You could rewrite your system to have the Touched event on the client, and then use a single RemoteEvent (with the same security logic you currently have in place, with some additional magnitude checks) in place of the server-side Touched event. Then use TouchedEvent:Disconnect() on the client, thus reducing network traffic.

Have you considered making a custom touched event? You can probably take advantage of raycasting to achieve this.

Pro - I was just looking at this but I don’t think LocalScripts can subscribe to Touched events.

Tenal - I’ve been resisting it but it might be required.

2 Likes

Of course they can, why wouldn’t they be able to? :stuck_out_tongue:

Edit: Just tested it out, they can!

You could use remotes instead, so, when a Touch event fires on the client, a remote is fired, you could add a rate limiter on the client and on the server to prevent it from firing too much.
e.g. if a remote is fired more than 20 times in a second, ignore the event/request for a short period of time.

I do debounces on both the server and the client. The remote event will only get fired once, unless the player is exploiting. That is why I also do it on the server, just to check that what the client sent is ok.

EDIT: I read that as remote events the first time, not touch events. But the same concept still probably applies.

If I’m understanding you correctly, you don’t want the sword to damage the player more than once. My proposed solution is to store all items in a table(You can also try using GetTouchingParts()):

local TouchedPlayers = {}
local function SwordSlash(Part)
    if Part.Parent:FindFirstChild("Humanoid") and not table.find(TouchedPlayers, Part.Parent) then
        table.insert(TouchedPlayers, Part.Parent)
        -- rest of code
    end
end

Part.Touched:Connect(SwordSlash)

Or, another solution is to use rays from the start position to the end position. So, it will only do damage when the attack ends:

local StartPosition = Character.Tool.Handle.Position
Animation:Play()
Animation.Completed:Wait()
local Ray = Ray.new(StartPosition, Character.Tool.Handle.Position)
workspace:FindPartOnRay(Ray)

It would be very strange but you could try doing something where after the first Touch event it deletes the hitbox and then you can generate a new hitbox when you can do damage again. I don’t know if this would work well though.

Well theres definitely multiple objects which could trigger the event, and you don’t want to mute the touched event if the sword first hits a wall prior to any limb of a character. It’d be interesting if the touchInterest took in a whitelist, but I also liked @VegetationBush’s idea on using a table. You could use it to filter out multiple triggers on the same part, but I wouldn’t go as far as disconnecting the event in case the sword hits non-character objects on its swing.

local activeParts={}
local characterDebounce=false

sword.Touched:Connect(function (part)
	if (activeParts[part] and activeParts[part].status()~='dead') or characterDebounce then
		return 	-- Part already being handled
	end
	activeParts[part]=coroutine.running()
    if isInCharacter(part) then
         characterDebounce=true
    end
	-- Code

end)

You could use a server-generated hitbox ascribed to each character. That way collision will be handled by the server rather than client

How about just a simple debounce like I use for my scripting involving touch events.
Debounce = false
function Touched ()
debounce = true
if debounce then return end

[CODE]
wait(TIME IN SECONDS)
debounce = false
end
script.Parent.Touched:Connect (Touched)
I use that general debounce idea for simple things such as regen buttons etc. The idea could be adapted for use in swords.

That doesn’t stop the Touched event from replicating. Please read the post before responding.

This should be able to satisfy your needs:


Similar topic

2 Likes

you could try to batch them together?

Every 0.1 seconds you generate a new table, and all hits that happen in that 0.1 seconds are put into it.
if the table has stuff in it, it’s sent to the server in a batch, and it goes through all entries for sanity checks.

Hits={}
Tick = 0

Sword.Touched:connect(function(hit) 
    if Hitcheck(Hit) then
        table.Insert(Hits,Hit)
    end
end)
game["Run Service"].RenderStepped:connect(function(delta)
    Tick = Tick + 1
    if Tick == 10 then
        Tick = 0
        if Hits[1] then
            -- send to server
            Hits = {}
        end
    end
end)
1 Like