RemoteEvents with temporary connections causing issues, how to fix?

It’s been a real long while since I was here, but I’m back with good reason (and needing help), this latest issue of mine has left me frustrated, drained, and stumped.

I’m working on implementing my own custom hitbox system for a side project of mine, it’s currently in a very rough state, but it should theoretically work…which it does, but it has this not-so-small issue that I’ll talk about further down this post.
It’s implemented like this:

  1. Once the player left clicks, a function starts running and a event is fired, the server starts its own function related to the player’s left click.
  2. As both functions runs nearly in sync, the server opens a temporary connection that varies on the player’s ping, through another event. It automatically disconnects once its time is up.
  3. The player handles the hitbox on the client, getting all enemies that were caught in it and sending it to the server to that same event.
  4. If the server receives the information in time, then it does a sanity check (not implemented) to see which enemies are vaild, and whatever the connected function was (usually damaging them).
    Alternatively if the server does not receive the information in time, then it does the hitbox itself and handles whatever the connected function was supposed to do (usually damaging them again).
    In short, this lets the player handle the hitboxes, but the server usurps that from them whenever they get too laggy.

Some additional notes:

  • All events are under ReplicatedStorage, as it’s shared between all players.
  • No new events are created.

With my base of my hitbox system completed, I wanted to try see if the temporary connection works…it does, but for whatever god forsaken reason, the it the previous “fire” gets sent instead of the current one. After some testing and experimenting, turns out it the previous “fire” is queued and gets sent to the server instead whenever it reconnects to the event, causing issues. I genuinely have no idea how to work around this.

Relevant client code snippet:

function class:M1Button()
	parentClass.M1Button(self)
	self.Animations.Attack:Play(0.3333)
	wait(0.5833)
	delay(0.25, function()
	    HitEvent:FireServer(HitboxCreator:GetEnemiesInBox(self.Character.HumanoidRootPart.CFrame:ToWorldSpace(CFrame.new(Vector3.new(0, 0, -5))), Vector3.new(4,5,6), Params, 3, Color3.new(0,1,0)))
	end)
	wait(0.1167)
	self.Animations.Attack:AdjustWeight(0, 0.1333)
end

Relevant server code snippet:

function class:M1Button()
	wait(0.5833 - self.root.Ping/2)
	local Success = false
	local Connection = HitEvent.OnServerEvent:Connect(function(Player, CaughtEnemies)
		if #CaughtEnemies == 0 then return end
		for _, Enemy in CaughtEnemies do
			--print("damage done from the cilent")
			Enemy.Humanoid.Health -= 20
			Enemy.Head.Color = Color3.new(0,1,0)
		end
		Success = true
	end)
	wait(self.root.Ping)
	Connection:Disconnect()
	if not Success then
		local rootcframe : CFrame = self.Character.HumanoidRootPart.CFrame
		local CaughtEnemies = HitboxCreator:GetEnemiesInBox(rootcframe:ToWorldSpace(CFrame.new(Vector3.new(0, 0, -5))), Vector3.new(4,5,6), Params, 3)
		for _, Enemy in CaughtEnemies do
			--print("damage done from the server")
			Enemy.Humanoid.Health -= 20
			Enemy.Head.Color = Color3.new(1,0,0)
		end	
	end
end

It’s important that my way of implementing the system should (roughly) remain the same, as I feel like it’s the only way than I could create fair hitboxes (and also because I’m going to reuse it for important project of mine, so I can’t really change much how its implemented).

Or perhaps am I being too stubborn with wanting it remain roughly the same, and there’s a easier alternative solution to this?

Sooo after nobody responded back to me for the past few days, I had to solve this myself. Whatever is the metaphorical equivalent of smashing your head against the computer is.

There’s two solutions that I’ve found to this problem of mine.

SOLUTION #1:
Just simply have another connection to the event, that does jack squat. This effectively stops the client fires being “queued” and are tossed away. It’s the easy solution to this, but it feels like that’s just cheap.

SOLUTION #2:
Have special function connected to the event, where it relays all arguments received to any functions in a array, these functions can be removed from the array to emulate being disconnected.

To be honest these doesn’t feel like solutions to me, more of a hack to be honest, so I’m still willing to hear anyone else on any alternative solutions. Unless…these are really the best solution to this.

1 Like

Addendum A:
Anyhow since I’ve solved this problem of mine, I figured it would be useful for everybody if I shared snippets of the code I’ve done for it. They won’t work right out of the box if you copy-n-paste it as is, as I made it in mind to work within my systems, but if you have good enough knowledge of how Lua works, you can take it apart and re-implement it for yourself. Shouldn’t be too hard. Also added some comments explaining how it works.

The “temporary connections manger” snippet:

HitEvent.OnServerEvent:Connect(function(Player, ...)
	if Player ~= self.Player then return end  --checks if player is correct one to listen for
	local args = {...}
	for _, Connection in self.TempConnections do  --runs through a array with functions
		if Connection.Closed then continue end  --since this already closed, skip this one
		Connection.Success, Connection.Closed = Connection.OnReceive(Player, unpack(args))
		--success = has temp connection deemed that it succeed? uses return
		--closed  = kinda useless, but great if one wants to enable the connection to receive a limited amount of fires
	end
end)

The “create temp connection” function:

function class:OpenTempConnection(TotalTime, OnReceive, OnFailure)
	local TempConnection = {}
	TempConnection.Success = false
	TempConnection.Closed = false
	TempConnection.OnReceive = OnReceive
	TempConnection.OnFailure = OnFailure
	--creates new table to track success, is closed, and what funcs to run
	
	table.insert(self.TempConnections, TempConnection)
	wait(TotalTime)
	if not TempConnection.Success then  --has server has not received event in time?
		TempConnection.OnFailure()  --run the function for failure
	end
	table.remove(self.TempConnections, table.find(self.TempConnections, TempConnection))
	return if TempConnection.Success then true else false
end

There might be one more addendum coming? I dunno, don’t count on it.

if I understood correctly; disconnecting the event and reconnecting it after a bit of time still receives the old data while it was disconnected?

Can you forcibly disconnect the event? .I.E. calling Disconnect then Destroy.

Another possible solution is to not disconnect the event but to receive then dropping the data if we’re not actively waiting for a response.

I think roblox is trying to do the same thing you are doing (compensating for ping).

Oh nonononono, it actually queues the data and uses it whenever the event is reconnected, as I know that I done plenty of testing to figure out what’s wrong.

Yes, I already tried disconnecting it, but for the aforementioned thing happens. Can’t really use destroy() either as I want to avoid creating and destroying the same event over and over again.

I thought about doing that, but decided against it as I’ll be using the same event for other things, and needs to handle whatever values are thrown into it.

Actually not quite, as I’ve already discovered this about roughly two weeks ago, Roblox receives the data whenever the client’s fire passes the server-client boundary. It does not “compensate for ping”, while my system kinda does that in sense, it just…does the thing itself instead waiting for the client when it takes too long.

I know you’re trying to help, but I guess I’ve already solved this.

1 Like