ClientCast - A Client-based, Idiosyncratic Hitbox System!

Introduction

ClientCast is a performant solution to melee hitboxes, allowing you to provide a smooth and lagless combat experience for your players. Why? Because ClientCast was designed with User Experience (UX) in mind - by calculating the hitbox on the client, not only is there no visual delay for when the player hits someone, but the player won’t miss the hitting targets due to a player’s ping! Not only is it easy for the client to have a smooth combat experience, but it’s also made extremely easy for the developer to implement! To keep maximum customizability in mind, ClientCast does not restrict you in any way - this means ClientCast does not have its own debounces and ignore lists so that you can customize it just the way you want.

Why this module?

ClientCast has been designed with easy client-server communication in mind - something no other module has done, as all of them are server-based. Not only that, but ClientCast has been designed with ease-of-use in mind in general - want to detect when a hitbox hits a humanoid? Simply connect to the Caster.HumanoidCollided event!

This module is very easy to use, and costs you nothing - your benefits? smooth combat and happy players!


How to Use

General tips if you set the ClientCaster’s owner to a client:

  • Validate whether the actual instance that was hit is close enough / accurate, as exploiters could spoof this value whenever it’s being sent over to the server. There is no one reliable way to verify this, and as such, you should program your own solutions depending on the context.

  • Validate whether or not the hit position is too far away to prevent exploits. ClientCast does not do this for you.

Example:

Caster.Collided:Connect(function(Data)
    if (Data.Position - Caster.Object.Position).Magnitude > 5 then
        return print("very likely an exploiter")
    end
end)

If you would like to take into account for the player’s ping, you can easily get their ping through Player:GetNetworkPing().


Links

Latest release (Release.rbxm is the module): Releases · Pyseph/ClientCast · GitHub
ClientCast Wiki: ClientCast

Script-injection behavior

The ClientCast module has a Settings table at the top of the script, which you can edit to suit your needs.
ClientCast automatically sets everything up for you so long you require ClientCast at least once, meaning it creates a RemoteEvent, parents localscripts to all clients and so on. If you wish to disable this behavior and manually edit the setup, set AutoSetup in the Settings table to false.

Please note that neither Collided nor HumanoidCollided have limits on how many times they can hit an object that is already hit, so make sure to implement your own debounces and checks.


Example Usage

hitboxes for our game’s weapons rely on a modified version of ClientCast:

Example usage along with a sample video can be found here: ClientCast


Check out my other stuff!

342 Likes

Out of curiosity, where do you get the notion that the server performing raycasts is laggy? Both peers are capable of firing off hundreds of rays with little expense provided your casts are short, which in this case they are very short and don’t (or shouldn’t) have a huge impact on performance.

Ultimately indeed the client can handle even more casts than the server but I don’t believe you give the server enough credit for its capability to handle raycasting. It won’t “eventually” lag players. Raycasting is a one-off solution. If you encounter eventual lag, you need to do a deeper delve into your system because something else is almost definitely causing that problem.

18 Likes

At most exploiters will be able to make the hitbox bigger by 2 studs or so (2 studs from point of collision, not the whole hitbox). I’ve talked about this in my thread regarding .Touched

3 Likes

While this is true, imagine a 700-player server in a huge MMORPG (our game basically) - 700 players constantly attacking and slashing would nevertheless have some impact. The most important feature of this module would be how it handles ping and latency though.

13 Likes

What I would do is when the sword swings it clones the sword every few mili seconds, then it takes apart the sword gets rid of the unnecessary stuff inside of the sword makes it invisible, there’s your hitbox.

3 Likes

The idea of Raycast Hitboxes is providing a more accurate hitbox for objects, weapons etc… by default the .Touched event is not that accurate as It should be, that’s why the community found its way out to create accurate hitboxes using Rays, Regions and some other functions not related to .Touched.

Also, I don’t really know if clonning an object every few mili seconds would be a great idea, not only for the lag, but also for the client performance.

6 Likes

You should mention that explicitly in the thread. All you’ve said is that it takes into account ping and latency, which doesn’t imply what you just said.

3 Likes

…?
Obviously I didn’t go in as much detail on this thread, but on the GitHub it’s all written and explained. I’m not going to show all methods and functions that a ClientCaster has on this thread - that’s what the ClientCast github wiki is for.

3 Likes

[Update 1.0.1]

Expanded ClientCast.Settings table:

  • Added Settings:GetDebugColor method
  • Added Settings:GetDebugMode method
  • Added Settings:GetDebugLifetime method
  • Added Settings:SetDebugLifetime method
  • Added Settings:SetDebugColor method

Getting and setting debug lifetime, color and-mode by modifying the Settings table directly during runtime is now deprecated. Using these newly implemented methods is advised.

9 Likes

How do you stop the casting?
I’m doing:

Caster:Stop()

Doesn’t work, and still blasts off with 50 events per second.

Is it a bug, or am I doing it incorrectly?

3 Likes

Could you send me a repro file showcasing the issue?

2 Likes

I’m afraid I cannot, but I can send you the original script that I have right now:

-- // Constants \\ --
-- [ Services ] --
local Services = setmetatable({}, {__index = function(Self, Index)
	local NewService = game:GetService(Index)
	if NewService then
		Self[Index] = NewService
	end
	return NewService
end})

-- [ Modules ] --
local CooldownCache = require(Services.ServerScriptService.Modules.CooldownCache)
local ClientCast = require(Services.ServerScriptService.ClientCastHolder.ClientCast)

-- [ LocalPlayer ] --
local LocalPlayer = script:FindFirstAncestorOfClass("Player")

-- [ Tool ] --
local Tool = script.Parent

-- // Variables \\ --
local Cooldown = CooldownCache.new(1)

-- // Main \\ --
local Caster = ClientCast.new(Tool.Handle, RaycastParams.new())
Caster:SetOwner(LocalPlayer)

Caster.HumanoidCollided:Connect(function(RaycastResult, Humanoid)
	for i,v in ipairs(Services.Players:GetPlayers()) do
		if Humanoid.Parent == v.Character then
			return
		end
	end

	local Character = Humanoid.Parent
	local HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
	Caster:Stop()
	Humanoid:TakeDamage(5)
	print(Humanoid:GetFullName())
end)

Tool.Attack.OnServerEvent:Connect(function(Player)
	if Player == LocalPlayer and Cooldown:IsReady(Player.UserId) then
		Caster:Start()
		Cooldown:Update(Player.UserId)
		Caster:Stop()
	end
end)

Caster:Stop()
3 Likes

I need a short script which reproduces the bug 100% of the time. I do not have access to your modules - if anything I’d assume this is an issue with your Cooldown module. I do not debug issues that could possibly not even concern me in the first place.

3 Likes

I made a small example
Example broken.rbxl (33.4 KB)
It kills the humanoid then does not stop after doing damage.

1 Like

I’ve pushed an update to ClientCast.lua on the GitHub. Could you try replacing your ClientCast ModuleScript’s source with the new version, and see if the bug has been addressed?

5 Likes

Yes, I did it, and it works!
Thank you for the quick update.

A quick notice, I believe you have two values named “Debug”, one is a boolean, and the other is a method. Lua seems to confuse these two, I hope you can fix that some time.

3 Likes

I’ll push an update to change the Debug method to SetDebug and GetDebug. Unsure when I’ll push the update though, as I want to add a way to also get a player’s ping easily for the next update.

6 Likes

Hey I checked his module out a month or so ago, and I see you have been working on it still, great!

I need a solution to help me visually sync up ranged attack in my game, these are slow-moving projectiles that are tweened on the client while the hitbox (using .Touched) is tweened on the sever. I am of course getting some de-syncing between the damage being a applied and the visual effects being played on the client.

I am considering between this module and RaycastHitbox module. How does your module differ from RaycastHitbox in terms of my use case? My primary issue is that I want to make hits appear as smooth as possible between server an client, how exactly can you perform calculation on the client and trust anything they send to the server?

What is to stop the player from moving the hitbox wherever they want it to go?

2 Likes

Basically, ClientCast takes the load off of using .Touched on server. On the other hand its slightly more exploitable, maybe by one or two studs. Although for fast moving objects, its much better for you to use FastCast for accuracy, this is mostly for melee hitboxes.

2 Likes

These are slow moving, and large, projectiles and fastcast is way overkill for my needs. In short, I’m trying to figure out how to show a projectile hitting on the client at the same time the server hit box does the hit

2 Likes