ClientCast - A Client-based, Idiosyncratic Hitbox System!

Aha, should’ve tried that in my testing. I didn’t check versions. :sweat_smile:

Hello!

I’ve been using this module for around a week now (Got the latest release from GitHub.), and noticed something hard to ignore.
Whenever a player’s ping exceeds 100ms, the hitbox becomes extremely innacurate and inconsistent.
(Side note; I myself average ~200ms on games, and so do most of my friends, which is why this is hard to ignore.)
In studio, where ping is practically non-existant, during playtests, this issue does not occur.

The way I’ve got it set up is it creates the caster object, sets it’s owner to the player, and then when the attack is to be performed, it calls :Start(), before delaying for 0.25 seconds, and calling :Stop().
As for sanity checking, it measures the magnitude between the weapon and the RaycastResult.Position with a threshold of 8.5 (I know, it’s very forgiving.)
That’s as brief as I can explain it, with minimal code for reproduction.

Am I doing something wrong, or is this expected behavior?
Any help is much appreciated!

The same exact thing happens to me aswell, if you have over 200 ping the hitboxes basically do not work at all.

Cc. @tinyponds
I’ve set my incoming replication lag to 0.3 seconds (totalling for 0.3*2 = 600ms ping) and it’s been working fine. Could you try delaying between when you call :Stop(), and make sure to not accidentally call stop when another attack is already being performed? My gut instinct says it’s possibly caused by you :Stop()ing the caster before any hit information was received.

1 Like

I’ve just been using Destroy() :skull:. I will instead use :Stop().

You’re definitely right about that though, I’ve just made it so there’s no stopping at all and it functions perfectly fine given the high ping. What is your recommended workaround to this?

I have it set up so after a given duration it stops the casting, but I believe having it go until it actually hits something would make it feel janky, (outside of you know, having high ping).

Destroy()ing is definitely worse, since it’ll require re-connection between the client and server each time. I’m personally using a “fork” of my own module, in which the client calls :Start() and :Stop() on its own to eliminate ping issues. You could do something similar, though do note you’ll have to be careful to only accept collision calls on the server during hitboxes, and disconnect the event connection once ended.

1 Like

Would you mind sharing what this setup would look like? I’m a bit of a numptee when working with code written by people other than myself.

Thank you so much for being so helpful :smiling_face:.

Edit:

Also not out for you but, why wouldn’t you include this into the main set-up of the Module? Is there a specific reasoning as to this? You make everything very easy to understand and digest when it comes to set-up, would it make the process more complicated?

Sure, this is the module I use to create and update casters. It’s a simple wrapper around the Start and Stop methods, which determines whether the data should be sent to the client or not when starting it.

WeaponCasterModule.lua
--!strict
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")

local ServerModules = ServerStorage.Modules

local ClientCast = require(ServerModules.Combat.ClientCast)

local Module = {}
Module.HitboxCasters = {}

local CachedCharacters = {}
-- determine on whether it's necessary to send data to the client again.
-- Data should only be sent to the client once
local function GetCasterState(Character: Model, Serial)
	local CharacterCache = CachedCharacters[Character]

	if CharacterCache == nil then
		if Character then
			CachedCharacters[Character] = {}
		end

		return "Default"
	end
	if CharacterCache[Serial] == nil then
		return "Default"
	end

	return "NoReplication"
end

function Module.GetCaster(Character, Serial)
	local Player = Players:GetPlayerFromCharacter(Character)
	local SavedCaster = Module.HitboxCasters[Serial]

	if not SavedCaster then
		local RayParams = RaycastParams.new()
		RayParams.FilterType = Enum.RaycastFilterType.Whitelist
		RayParams.FilterDescendantsInstances = {
			workspace.Characters.Players,
			workspace.Characters.Mobs,
		}

		SavedCaster = ClientCast.new(Serial, RayParams, Player)
		Module.HitboxCasters[Serial] = SavedCaster
	end

	return Player and setmetatable({
		Start = function()
			SavedCaster:Start(GetCasterState(Player.Character, Serial))
		end,
		Stop = function()
			SavedCaster:Stop("NoReplication")
			CachedCharacters[Player.Character][Serial] = true
		end,
	}, {
		__index = SavedCaster,
		__newindex = SavedCaster
	}) or SavedCaster
end

local function OnPlayerAdded(Player)
	Player.CharacterRemoving:Connect(function(Character)
		CachedCharacters[Character] = nil
	end)
end

for _, Player in Players:GetPlayers() do
	OnPlayerAdded(Player)
end
Players.PlayerAdded:Connect(OnPlayerAdded)

return Module
MeleeWeaponModule.lua
local CasterHitConnection: RBXScriptConnection?
local Caster = WeaponCasterModule.GetCaster(Character, ItemData.Serial)

Caster:Start()

if CollidedConnections[Character] then
	CollidedConnections[Character]:Disconnect()
end
CasterHitConnection = Caster.HumanoidCollided:Connect(...)

CollidedConnections[Character] = CasterHitConnection :: RBXScriptConnection
2 Likes

Yeah, ClientCast is meant to be dead-simple, and messing around with replication behavior makes it a bit more complex than I’d want it to be. You can always fork the module with your own modifications if you feel something’s missing for your usecase!

1 Like

What would be the best practice security wise in this system? i thought of backtracking but im too novice for that and dont know if it would work well here.

Honestly, a simple magnitude check would suffice in my opinion.
The reasoning is that even with server-side hitboxes, nothing prevents a hacker from teleporting right next to the target and making their hit - same applies to client-side hitboxes.

1 Like

Another thing, whats the best way to generate attachment points? I have tried the one used in the examples but it doesnt work consistently

Anybody else experiencing heavy lag after ten mins of running a server with this module? Im making a combat system, and its fine in studio. But in game the rate(/s) starts sky rocketing and the ping gets very high. I narrowed the issue down to this module. Has anybody ran into this problem?

Sorry for the late response.
This should be caused by you not disconnecting the caster by calling :Stop(), and also likely creating more caster objects each time. This causes the client to send more data to the server and increase ping.

I was calling :Stop() though.

image

if anyone is interested i made these simple functions to easily spread the attachment points on the longest axis and a bit of some of other stuff

local offestfromeachpos = 0.7

local function makeattachment(offsetvec, part)    
    local attachment = Instance.new("Attachment", part)
    attachment.Position = offsetvec
    attachment.Name = "DmgPoint"
end

----------------------------------------------------------------------------
local function spreadAttachements(part, axis)
    local relativesize = (part.Size[axis] / 2)
    for i = -relativesize, relativesize, offestfromeachpos do        
        makeattachment(Vector3.new(i * (axis:lower() == "x" and 1 or 0), i * (axis:lower() == "y" and 1 or 0), i * (axis:lower() == "z" and 1 or 0)), part)
    end
end
local function spreadAttachementsOnLongest(part)
    if part.Size.X > part.Size.Z and  part.Size.X > part.Size.Y then
        spreadAttachements(part, "X")
    elseif part.Size.Z > part.Size.X and part.Size.Z > part.Size.Y then
        spreadAttachements(part, "Z")
    elseif part.Size.Y > part.Size.X and part.Size.Y > part.Size.Z then
        spreadAttachements(part, "Y")
    end
end
1 Like

To save myself time can i ask what are the ways this system can be exploited?

Hi, does anyone know how to fix my problem with ClientCast? I am trying to use it to detect collision with a hammer weapon type. But when I use it, sometimes a get an error that states
“Remote event invocation queue exhausted for ReplicatedStorage.ClientCast-Replication; did you forget to implement OnServerEvent? (128 events dropped)” I am not sure as to how to resolve this issue.

Here is a video showcasing this issue: Remote event invocation queue exhausted - YouTube The error comes up towards the end, feel free to skip the beginning.

Hey I was using this module for my combat and I got a question…
It is better to make a new Caster (local Caster = ClientCast.new()) for every m1 or 1 for all the m1s in the sword
Nice module btw

Ideally, reuse each caster as much as possible. I use one for each weapon object and not just m1 combo, though that depends on your code.

1 Like