ShapecastHitbox: For all your melee needs! [V.0.2.5]

Hey there.

I understand that there is upcoming API, but as i am a relatively new scripter. I still dont quite understand how to properly setup the hitbox even with the api listed above, if at some point you will have a noob friendly up to date api I would love to understand how it works!

also great work on the module, :smiley:

1 Like

HI, have you tried the playground? The sword script and rotating axe script is relatively simple to follow. What parts have you tried and stuck on?

1 Like

Thank you for the explanation. I look forward to future updates on this module.

1 Like

Hi I’ve started implementing ShapecastHitbox into my game and I love the plugin too. Thank you for making something so amazing. I just have a bug that I can’t seem to get around with how OnHit() works with HitStop()

From one of my client scripts I’m passing in multiple parameters like combo, chargeLevel, etc which are used in calculating final damage in a server script. For some reason even when calling HitStop(), it seems like the OnHit() connection only uses the variables passed on the very first creation of the OnHit() listener. I’ve tried assigning it to a connection variable but it won’t allow me to disconnect it as it’s not a connection object being made in the first place.

The only way I’ve managed to get around this is destroying and recreating the hitbox on every call, but this feels like it would be very unoptimized especially when player counts get larger. Is this intended or is there something I’m missing? Below is the code.

Thank you so much for your hard work! I look forward to future updates.

type or paste code herefunction HitboxClient.CreateGearHitbox(hitboxPart, typeSort, dualWielding)
	
	if not dualWielding then
		if HitboxClient.weaponHitbox then HitboxClient.weaponHitbox:Destroy() end

		local gearHitbox = shapeCastHitbox.new(hitboxPart)
		HitboxClient.weaponHitbox = gearHitbox
	else
		if HitboxClient.weaponDualWieldHitbox then HitboxClient.weaponDualWieldHitbox:Destroy() end
		
		local gearHitbox = shapeCastHitbox.new(hitboxPart)
		HitboxClient.weaponDualWieldHitbox = hitboxPart
	end
	
end

function HitboxClient.StartMeleeHit(lightOrHeavy : string, combo : number, invID : number, chargeLevel : number)
	
	local hitbox : shapeCastHitbox.Hitbox = HitboxClient.weaponHitbox
	local targetsHit = HitboxClient.targetsHit

	hitbox:HitStart():OnHit(function(raycastResult, segmentHit : shapeCastHitbox.Segment)
		
		
		local playerTargetHit = raycastResult.Instance.Parent:FindFirstChild("Humanoid")
		local enemyNPCTargetHit = raycastResult.Instance.Parent:FindFirstChild("NPC")
		
		if targetsHit[playerTargetHit] == true then return end
		if targetsHit[enemyNPCTargetHit] == true then return end
		
		if playerTargetHit then
			
			-- TODO FIX CONNECETION ISSUE HITBOX KEEPS REUSING OLD HITSTART
			
			targetsHit[playerTargetHit] = true
			signal.FireServer("CharacterCombatServer:MeleeHitPlayer", playerTargetHit, lightOrHeavy, combo, raycastResult.Position, invID, chargeLevel)
			
		end
		
		if enemyNPCTargetHit then
			targetsHit[playerTargetHit] = true
			
			-- TODO
		end
		
		
	end)
	
end

function HitboxClient.StopMeleeHit()
	
	local hitbox = HitboxClient.weaponHitbox
	table.clear(HitboxClient.targetsHit)
	
	hitbox:HitStop()
	
end
1 Like

Hi there, have you tried using OnStopped callback? It has a parameter that will auto clean up every callbacks used so far, which should fix your issue. You can chain it anywhere as well.

hitbox:HitStart():OnHit():OnStopped(function(cleanCallbacks)
     -- Will clean up the callbacks. Next iteration of hitbox called will only happen once.
     cleanCallbacks() 
end)
1 Like

Thank you that fixed it! If I could just ask one more question about segment hit and raycast Result thats returned from OnHit(). In my server sanity checks, I’m checking both that the hitbox part the DmgPoints are parented to is near the character model hit and also that the blockcast that hit the character model is in range of the hitbox part to a reasonable degree. Is raycastResult position the parameter I should be sending over for that? I’m worried that exploiters will modify the size of the block cast coming from each DmgPoint attachment on the hitbox.

Thank you once again.

	-- 3. Tight distance check from hitbox to target
	local validHitbox
	local validSegment
	for _, hitbox in weaponHitboxPart do
		if (weaponHitboxPart.Position - targetHRP.Position).Magnitude > MAX_TIGHT_HITBOX_DISTANCE then
			warn(playerAttacker.Name .. hitbox ..  " flagged: Weapon hitbox too far from target")
			continue
		else
			-- One hitbox was valid
			validHitbox = true
		end

		-- 4. Segment hit position must lie within hitbox bounds (w/ padding)
		local hitboxCFrame = weaponHitboxPart.CFrame
		local relativeHitPos = hitboxCFrame:PointToObjectSpace(raycastResult.Position)
		local halfSize = weaponHitboxPart.Size / 2

		-- Clamp the hit position to the hitbox's local bounds
		local clamped = Vector3.new(
			math.clamp(relativeHitPos.X, -halfSize.X, halfSize.X),
			math.clamp(relativeHitPos.Y, -halfSize.Y, halfSize.Y),
			math.clamp(relativeHitPos.Z, -halfSize.Z, halfSize.Z)
		)

		-- Compute how far outside the hit was
		local outsideDistance = (relativeHitPos - clamped).Magnitude

		-- If outside the shell padding, flag the player
		if outsideDistance > HITBOX_PADDING then
			warn(playerAttacker.Name .. hitbox .. " flagged: Segment hit position outside hitbox (softshell)")
			continue
		else
			-- One segment was valid
			validSegment = true
		end
	end
	
	if not (validHitbox and validSegment) then
		warn(playerAttacker.Name ..  " flagged: Hitbox cheating")
		CharacterCombatAntiCheat.addCheatFlag(playerAttacker)
	end
1 Like

So i have this bug where the hitbox cast continues from where i last activated it, in the picture you can see it cast for like 30 studs, the only way around it is to destroy and make the hitbox again and again… is there a way to clean this up?

	hitbox:BeforeStart(function()

		self.Duration = 0.5
		hitbox.Active = true

	end):HitStart(self.Duration):OnHit(function(raycastResult, segmentHit)

		if hitbox.Active then
			hitbox.Active = false
			local hitCharacter = raycastResult.Instance.Parent

			print(Player:DistanceFromCharacter(hitCharacter.HumanoidRootPart.Position))

			--print("hit "..hitCharacter.Name)

			hitbox:HitStop()

		end

	end):OnUpdate(function(deltaTime)

	end):OnStopped(function(cleanCallbacks)

		cleanCallbacks() --- This will clean up this chain

	end)
3 Likes

Yes, you can send the position, but it’ll require a bit more effort to sanity check that.

You can also send the segment instance instead.

hitbox:OnHit(function(raycastResult, segment)
    -- Of course edit this
    signal:Fire(segment.Instance)
end)

The segment instance references the actual DmgPoint attachment, and you can take the position, size, and radius attributes directly from the instance for sanity checks rather than relying on client given values.

Of course, the client can still spoof which instance it can send over, so take note of that. Segment doesn’t really give you where the player has hit exactly, but I assume you’ll be checking the range anyway so it should still fit the bill.

2 Likes

Sounds like a bug. I’ll fix it, stay tuned.

3 Likes

sorry for the mega late reply, in general i am simply not understanding how the api works, maybe im looking at it wrong. Yes i also did check the playground, i hear that it might be outdated compared to newer updates but im not 100% sure.

anyways my general issue was that the api wasnt easy to follow and Im not an expert by any means, so if theres a way that could help me out regarding the api issue that would be awesome : D

2 Likes

The only way you can patch it right now is to change HitStart’s Hitbox script to be using WorldPosition at hitbox:GetAllSegments() area, if you’re using attachments only. Gonna see if there’s a way to band-aid it.

1 Like

Right now, this is my current band-aid code I can do on it.

for _, segment in hitbox:GetAllSegments() do
	segment.Position = segment.Instance and segment.Instance.ClassName == "Attachment" and segment.Instance.WorldPosition or Solvers[segment.Instance.ClassName]:ToPosition(segment)
	segment.Distance = 0
end
3 Likes

Hm… Ant no one else has seen this happen in their game? Weird because it looked to me like the last position of the Atts wasnt reset for some reason but i get the same effect when i implement it in a local script instead of a module…quite strange… I guess ill try out shapecasts
Im doing this imersive wound system for bruises cuts and such on your limbs head torso so your system helps a lot i was just too used to your taycast module didnt try out the new stuff

2 Likes

Yeah it happened to me too, but since you mentioned it I’m just waiting for a fix now

2 Likes

Does this work to the detect high speed moving object? for example a bouncepad that can detect a player even if the player is dashing through it

1 Like

I’m so glad your team is working on such an amazing new resource. I’m excited to finally have a high quality module with the best of both worlds! Good luck!

1 Like

The hitbox needs to be moving, if the character has a hitbox on him then you can detect it using its hitbox

2 Likes

another thing users need to pay attention to is players playing custom animations… cheating… since animations replicate from client to server that can create issues if someone creates an animation of them spinning leats say like a bayblade and then runs it on your server. hackers use this for trolling and inapropriate jokes but in case of this module it can be used as a …weapon of sorts

2 Likes

@AndIdDoStuff @scribblestack

Hi there, thank you for your patience. I have updated the module, could you try again to see if the bugs are still there for you?

@XarkDev

I have added some additional checks, but it is not 100% foolproof due to how fundamentally casting is developed on Roblox. I would recommend splitting up the box into multiple, smaller cubes, like so:

This way, even when I’m standing still and the hitbox is created on top of me, it’ll still register:

If for some reason it still isn’t working, adjust ShapecastHitbox → Settings → Minimum_Stationary_Length to be higher.

Adjusting it higher will yield better results when the hitbox isn’t moving, but can induce more inaccurate results (as it expands the hitbox a bit further to compensate)

2 Likes

@AndIdDoStuff had a good suggestion there, but yes generally this module is designed in a way where high speed objects can be used. Like what the user suggested, having the hitbox on the player itself rather than the bounce pad then listening if ShapecastHitbox has hit anything tagged with bounce pad should work fine. I recommend listening the hitbox on the player’s client, as the player can update the hitbox at a higher resolution, meaning better accuracy and better detection at higher speeds.

The playground also has a fan example, which acts like a bounce pad touch emitter.

If you want to put hitboxes on the player, refer to the above post from this one. You’d want to split up the hitbox up into multiple parts so there are more opportunities for a hit to occur.

3 Likes