Hitbox Module by Salvatore

Salvatore, The hitbox is amazing, i did a modification to it , I just added more accurate collisions using bolt, it also won’t work for example if there’s a wall between the target and you, even if it’s too thin if you want to take a look :

--[[
	by SalvatoreScripts
	Last Updated: March 31st, 2026
	DevForum: https://devforum.roblox.com/t/hitbox-module-by-salvatore/3913281
	-- modified by spook in july 5 2026
		
	-- Updated to use Bolt for accurate swept collision (shapecasting) --
	
	Settings:
	
	LatencyCompensation: Distance compensation for lag so client hitboxes are instant.
	MaxLatencyCompensation: Max distance compensation for so, uping this value risks exploiting.
	Storage: Where new parts created with .Part() are stored.
	Whitelist: Whitelists where characters can be found.
	Debugging: Enabled/Distables debugging capability for all hitboxes.
]]

local Bolt = require("@game/ReplicatedStorage/Libraries/bolt")

local Settings = {
	LatencyCompensation = 20,
	MaxLatencyCompensation = 50,
	Storage = workspace,
	Whitelist = workspace,
	Debugging = true
}

--

local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local Signal = require(script:WaitForChild("Signal"))
type Signal<T...> = Signal.Signal<T...>

local Event, Function = nil, nil
if RunService:IsServer() then
	Event = Instance.new("RemoteEvent")
	Event.Parent = script
	Function = Instance.new("RemoteFunction")
	Function.Parent = script
elseif RunService:IsClient() then
	Event = script:FindFirstChildOfClass("RemoteEvent") or script:WaitForChild("RemoteEvent")
	Function = script:FindFirstChildOfClass("RemoteFunction") or script:WaitForChild("RemoteFunction")
end
Event = Event :: RemoteEvent
Function = Function :: RemoteFunction

export type hitbox = {
	Started: Signal<>,
	Stopped: Signal<>,
	Hit: Signal<({Model}, ...any)>,
	TouchBegan: Signal<Model>,
	TouchEnded: Signal<Model>,
	Destroying: Signal<>,

	Start: (self: any, Timer: number?, Debounce: number?, ...any) -> nil,
	Stop: (self: any) -> nil,
	Clear: (self: any) -> nil,
	Once: (self: any, ...any) -> {Model},
	Weld: (self: any, Part: BasePart, Offset: CFrame?) -> Weld,
	AssignNetwork: (self: any) -> nil,
	IsActive: (self: any) -> boolean,
	IsTouching: (self: any, Character: Model) -> boolean,
	GetHitCharacters: (self: any) -> {Model?},
	HitCharacters: (self: any, Characters: Model | {Model}) -> nil,
	Destroy: (self: any) -> nil,
}

export type Hitbox = {
	Part: (
		Size: Vector3 | number, 
		WeldTo: BasePart?, 
		Offset: CFrame?
	) -> Part,
	New: (
		Focus: BasePart, 
		Parameters: OverlapParams?, 
		Client: Player?,
		Debug: boolean?
	) -> hitbox
}

local Hitbox = {}
Hitbox.__index = Hitbox

local Hitboxes = {}
local Clients = {}

local partShapeCache = setmetatable({}, {__mode = "k"})

local function getPartShape(part: BasePart)
	if partShapeCache[part] then
		return partShapeCache[part]
	end

	local shape
	-- bounding box fallback
	if part:IsA("MeshPart") or part:IsA("UnionOperation") then
		shape = Bolt.create_box(part.Size)
	else
		local success, s = pcall(Bolt.create_from_part, part)
		shape = success and s or Bolt.create_box(part.Size)
	end

	partShapeCache[part] = shape
	return shape
end

local function getSweptAABB(cframe1: CFrame, cframe2: CFrame, size: Vector3)
	local p1 = cframe1.Position
	local p2 = cframe2.Position
	local min = Vector3.new(math.min(p1.X, p2.X), math.min(p1.Y, p2.Y), math.min(p1.Z, p2.Z))
	local max = Vector3.new(math.max(p1.X, p2.X), math.max(p1.Y, p2.Y), math.max(p1.Z, p2.Z))

	-- expand so no parts missed
	local expansion = math.max(size.X, size.Y, size.Z)
	min = min - Vector3.new(expansion, expansion, expansion)
	max = max + Vector3.new(expansion, expansion, expansion)

	local center = (min + max) / 2
	local sweepSize = max - min

	return CFrame.new(center), sweepSize
end

local wallShapeCache = setmetatable({}, {__mode = "k"})

local function getWallShape(part: BasePart)
	if wallShapeCache[part] then return wallShapeCache[part] end
	local shape
	if part:IsA("Part") then
		local ok, s = pcall(Bolt.create_from_part, part)
		shape = ok and s or Bolt.create_box(part.Size)
	else
		shape = Bolt.create_box(part.Size)
	end
	wallShapeCache[part] = shape
	return shape
end

local PROBE_SHAPE = Bolt.create_sphere(0.3)

local function isBlockedByWall(
	self,            
	hitboxShape,
	previousCFrame:  CFrame,
	currentCFrame:   CFrame,
	sweepDir:        Vector3,
	targetPart:      BasePart,
	overlapParams:   OverlapParams,
	attackerRoot:    BasePart?
): boolean

	local sweepLen   = sweepDir.Magnitude
	local targetPos  = targetPart.Position
	local targetChar = targetPart.Parent

	local excludeSet = {}
	for _, inst in ipairs(overlapParams.FilterDescendantsInstances or {}) do
		excludeSet[inst] = true
	end
	excludeSet[targetPart] = true
	if targetChar then excludeSet[targetChar] = true end

	local function isExcluded(part: BasePart): boolean
		if excludeSet[part] then return true end
		local p = part.Parent
		while p do
			if excludeSet[p] then return true end
			p = p.Parent
		end
		return false
	end

	local function getCandidates(cf: CFrame, size: Vector3): {BasePart}
		local parts = workspace:GetPartBoundsInBox(cf, size)
		local solid = {}
		for _, p in ipairs(parts) do
			if p.CanCollide and not isExcluded(p) then
				table.insert(solid, p)
			end
		end
		return solid
	end

	local rootPos = attackerRoot and attackerRoot.Position or previousCFrame.Position
	local rootToTarget    = targetPos - rootPos
	local rootToTargetLen = rootToTarget.Magnitude

	if rootToTargetLen > 0.1 then
		local rMin = Vector3.new(
			math.min(rootPos.X, targetPos.X),
			math.min(rootPos.Y, targetPos.Y),
			math.min(rootPos.Z, targetPos.Z)
		) - Vector3.new(2, 2, 2)
		local rMax = Vector3.new(
			math.max(rootPos.X, targetPos.X),
			math.max(rootPos.Y, targetPos.Y),
			math.max(rootPos.Z, targetPos.Z)
		) + Vector3.new(2, 2, 2)

		local rCenter = (rMin + rMax) * 0.5
		local rSize   = rMax - rMin
		local rootCF  = CFrame.new(rootPos)

		for _, wall in ipairs(getCandidates(CFrame.new(rCenter), rSize)) do
			local wallShape = getWallShape(wall)
			local hit, dist = Bolt.gjk.shapecast_simple(
				rootCF,
				rootToTarget,
				PROBE_SHAPE,
				wall.CFrame,
				wallShape,
				1e-3
			)
			if hit then
				local d = dist or 0
				if d < rootToTargetLen - 0.52 then -- if collision slips through thin walls, decrease this...
					return true
				end
			end
		end
	end

	if sweepLen >= 0.01 then
		local sweepCF, sweepSize = getSweptAABB(previousCFrame, currentCFrame, self.Focus.Size)

		for _, wall in ipairs(getCandidates(sweepCF, sweepSize + Vector3.new(1, 1, 1))) do
			local wallShape = getWallShape(wall)
			local hit, dist = Bolt.gjk.shapecast_simple(
				previousCFrame,
				sweepDir,
				hitboxShape,
				wall.CFrame,
				wallShape,
				1e-3
			)
			if hit then
				local d = dist or 0
				if d < sweepLen then
					return true
				end
			end
		end
	end

	return false
end

Players.PlayerRemoving:Connect(function(Client: Player)
	Clients[Client] = nil
end)

function Hitbox.Part(Size: Vector3 | number, WeldTo: BasePart?, Offset: CFrame?)
	local part = Instance.new("Part") 
	part.CanCollide = false
	part.Transparency = 1
	part.Color = Color3.fromRGB(255, 0, 0)
	part.Material = Enum.Material.SmoothPlastic
	part.TopSurface = Enum.SurfaceType.Studs
	part.BottomSurface = Enum.SurfaceType.Studs
	part.FrontSurface = Enum.SurfaceType.Studs
	part.BackSurface = Enum.SurfaceType.Studs
	part.LeftSurface = Enum.SurfaceType.Studs
	part.RightSurface = Enum.SurfaceType.Studs
	part.Shape = if typeof(Size) == "Vector3" then Enum.PartType.Block else Enum.PartType.Ball
	part.Size = if typeof(Size) == "Vector3" then Size else Vector3.new(Size, Size, Size)
	part.Anchored = if WeldTo == nil then true else false
	part.Massless = true

	part.CanQuery = false
	part.CanTouch = false

	local model = Instance.new("Model")
	model.ModelStreamingMode = Enum.ModelStreamingMode.Persistent

	part.Parent = model
	model.Parent = Settings.Storage

	if WeldTo then
		local weld = Instance.new("Weld")
		weld.Enabled = true
		weld.Name = "__HitboxWeld"
		weld.Parent = part
		weld.Part0 = part
		weld.Part1 = WeldTo
		weld.C1 = if Offset then Offset else CFrame.new()
	end

	return part
end

function Hitbox.New(Focus: BasePart, Parameters: OverlapParams?, Client: Player?, Debug: boolean?)
	if Hitboxes[Focus] then return Hitboxes[Focus] end
	Focus.Locked = true

	if RunService:IsServer() 
		and Focus.Anchored == false
	then Focus:SetNetworkOwner(nil) end

	assert(typeof(Focus) == "Instance" and Focus:IsA("BasePart"), "Hitbox 'Focus' must be an BasePart Instance.")
	assert(Focus:IsDescendantOf(workspace), "Hitbox 'Focus' must be a descendant of workspace.")

	local self = setmetatable({}, Hitbox)

	self.Focus = Focus
	self.Parameters = Parameters or OverlapParams.new()
	self.Client = if typeof(Client) == "Instance" and Client:IsA("Player") then Client else nil
	self.Offset = Focus:FindFirstChild("__HitboxWeld", true)
	self.Debug = Debug
	self.Started = Signal.new()
	self.Stopped = Signal.new()
	self.Hit = Signal.new()
	self.TouchBegan = Signal.new()
	self.TouchEnded = Signal.new()
	self.Destroying = Signal.new()

	self._Timer = nil
	self._Debounce = nil
	self._Trying = {}
	self._Touching = {}
	self._Characters = {}
	self._Args = {}

	self.__Timestamp = nil
	self.__Connection = nil
	self.__Debounce = nil
	self.__Destroying = Focus.Destroying:Once(function()
		self:Destroy()
	end)

	if self.Debug and Settings.Debugging then
		local clone = self.Focus:Clone()
		clone:ClearAllChildren()
		clone.Massless = true
		clone.CanCollide = false
		clone.Anchored = true
		clone.Color = Color3.fromRGB(255, 0, 0)
		clone.Transparency = 1
		clone.Name = "_Debug"
		clone.Parent = nil

		local sweepIndicator = self.Focus:Clone()
		sweepIndicator:ClearAllChildren()
		sweepIndicator.Massless = true
		sweepIndicator.CanQuery = false
		sweepIndicator.CanTouch = false
		sweepIndicator.CanCollide = false
		sweepIndicator.Anchored = true
		sweepIndicator.Color = Color3.fromRGB(255, 150, 0) -- orange = previous frame
		sweepIndicator.Transparency = 1
		sweepIndicator.Name = "_DebugSweep"
		sweepIndicator.Parent = nil

		clone:GetPropertyChangedSignal("Parent"):Connect(function()
			if clone.Parent == nil then
				clone.Transparency = 1
				sweepIndicator.Transparency = 1
				sweepIndicator.Parent = nil
			elseif clone.Parent == workspace then
				sweepIndicator.Parent = workspace
				local conn = nil
				conn = RunService.Heartbeat:Connect(function()
					if not clone or clone.Parent == nil then
						conn:Disconnect()
						return
					end

					local focusActive = self.Focus.Transparency == 1

					clone.CFrame = self.Focus.CFrame
					clone.Size = self.Focus.Size
					clone.Transparency = if focusActive then 0.6 else 1

					sweepIndicator.CFrame = self.PreviousCFrame or self.Focus.CFrame
					sweepIndicator.Size = self.Focus.Size
					sweepIndicator.Transparency = if focusActive then 0.8 else 1
				end)
			end
		end)

		self.__Debug = clone
		self.__DebugSweep = sweepIndicator
	end

	if RunService:IsServer() and self.Client then
		repeat task.wait() until Clients[Client]
		task.wait()

		local params = {}
		if Parameters then
			params.FilterDescendantsInstances = self.Parameters.FilterDescendantsInstances
			params.FilterType = self.Parameters.FilterType
			params.RespectCanCollide = self.Parameters.RespectCanCollide
			params.MaxParts = self.Parameters.MaxParts
			params.Tolerance = self.Parameters.Tolerance
			params.CollisionGroup = self.Parameters.CollisionGroup
			params.BruteForceAllSlow = self.Parameters.BruteForceAllSlow
		end

		local timestamp = os.clock()
		local success, result = false, false
		repeat 
			success, result = pcall(function()
				self.Client:RequestStreamAroundAsync(self.Focus.Position, 10)
				return Function:InvokeClient(self.Client, "New", self.Focus, self.Focus.Position, params, self.Debug)
			end)

			task.wait()
		until success == true and result == true
			or self.Focus == nil
			or os.clock() - timestamp > 60

		if success == false
			or result == false
		then 
			warn("Hitbox failed to offload to "..self.Client.Name.."'s client. Focus: ", self.Focus) return 
		elseif success == true and result == true then
			self:AssignNetwork()
		end
	end

	Hitboxes[Focus] = self
	return self
end


function Hitbox:AssignNetwork()
	if RunService:IsServer()
		and self.Offset 
		and self.Offset.Part1:IsDescendantOf(self.Client.Character) 
	then

		if self.Focus.Anchored == false then
			self.Focus:SetNetworkOwner(self.Client)
		end

		if self.Offset.Part1:IsDescendantOf(self.Client.Character) 
			and self.Offset.Part1.Anchored == false
		then
			self.Offset.part1:SetNetworkOwner(self.Client)
		end
	end
end

function Hitbox:Weld(Part: BasePart, Offset: CFrame?)
	self.Focus.Massless = true

	if self.Offset ~= nil then
		self.Offset.Part1 = Part
		self.Offset.C1 = if Offset then Offset else CFrame.new()

		self:AssignNetwork()
		return self.Offset
	end

	for i,weld in pairs(self.Focus:GetChildren()) do
		if weld:IsA("Weld") then weld:Destroy() end
	end

	local weld = Instance.new("Weld")
	weld.Parent = self.Focus
	weld.Part0 = self.Focus
	weld.Part1 = Part
	weld.C1 = if Offset then Offset else CFrame.new()

	self:AssignNetwork()
	return weld
end

function Hitbox:IsActive()
	return self.__Timestamp ~= nil
end

function Hitbox:IsTouching(Character: Model)
	if self.Offset ~= nil then
		local radius = self.Offset.C1.Position.Magnitude + 1
		if (self.Offset.Part1.Position - self.Offset.Part0.Position).Magnitude > radius then
			return false end
	end

	if typeof(Character) ~= "Instance" 
		or not Character:IsA("Model")
		or not Character.PrimaryPart
	then return false end

	local multiplier = .01
	local player = Players:GetPlayerFromCharacter(Character) :: Player?
	local ping = if player then player:GetNetworkPing() else 0
	local distance = Settings.LatencyCompensation
	distance = distance + (multiplier * ping * Settings.LatencyCompensation)
	distance = distance + (multiplier * Character.PrimaryPart.AssemblyLinearVelocity.Magnitude * Settings.LatencyCompensation)
	distance = math.clamp(distance, Settings.LatencyCompensation, Settings.MaxLatencyCompensation)
	local magnitude = (Character.PrimaryPart.Position - self.Focus:GetClosestPointOnSurface(Character.PrimaryPart.Position)).Magnitude

	return magnitude <= distance
end

function Hitbox:GetHitCharacters()
	return self._Characters or {}
end

function Hitbox:HitCharacters(Characters: Model | {Model})
	if typeof(Characters) ~= "table" then Characters = {Characters} end
	if #Characters == 0 then return end

	local characters = {}

	for i,character in Characters :: {Model} do
		if not table.find(self._Characters, character) then
			table.insert(self._Characters, character)
			table.insert(characters, character)
		end
	end

	if #characters > 0 then
		self.Hit:Fire(self._Touching, table.unpack(self._Args))
	end
end

function Hitbox:Once(...)
	local characters = {}

	local currentCFrame = self.Focus.CFrame
	local previousCFrame = self.PreviousCFrame or currentCFrame
	local direction = currentCFrame.Position - previousCFrame.Position

	if not self._BoltShape then
		self._BoltShape = Bolt.create_from_part(self.Focus)
	end
	local hitboxShape = self._BoltShape

	local sweepCF, sweepSize = getSweptAABB(previousCFrame, currentCFrame, self.Focus.Size)
	local candidateParts = workspace:GetPartBoundsInBox(sweepCF, sweepSize, self.Parameters)

	local tolerance = 1e-3

	local wallCheckParams = RaycastParams.new()
	local filters = {}
	if self.Parameters.FilterDescendantsInstances then
		for _, inst in ipairs(self.Parameters.FilterDescendantsInstances) do
			table.insert(filters, inst)
		end
	end
	wallCheckParams.FilterDescendantsInstances = filters
	wallCheckParams.FilterType = self.Parameters.FilterType or Enum.RaycastFilterType.Exclude
	wallCheckParams.IgnoreWater = true

	for _, part in ipairs(candidateParts) do
		local character = part.Parent
		if character and character:IsA("Model") and character:FindFirstChildOfClass("Humanoid") then
			if character:IsDescendantOf(Settings.Whitelist) and not table.find(characters, character) then
				local partShape = getPartShape(part)
				if partShape then
					local hit = false

					if direction.Magnitude < 0.01 then
						hit = Bolt.gjk.intersects(currentCFrame, hitboxShape, part.CFrame, partShape, tolerance)
					else
						hit = Bolt.gjk.shapecast_simple(previousCFrame, direction, hitboxShape, part.CFrame, partShape, tolerance)
					end

					if hit then
						local attackerRoot: BasePart? = nil
						if self.Offset and self.Offset.Part1 then
							local char = self.Offset.Part1.Parent
							if char and char:IsA("Model") then
								attackerRoot = char:FindFirstChild("HumanoidRootPart") :: BasePart?
							end
						end
						-- Fallback use the weld target itself
						if not attackerRoot and self.Offset then
							attackerRoot = self.Offset.Part1
						end

						local blocked = isBlockedByWall(
							self,          
							hitboxShape,
							previousCFrame,
							currentCFrame,
							direction,
							part,
							self.Parameters,
							attackerRoot
						)
						if blocked then
							hit = false
						end
					end

					if hit then
						table.insert(characters, character)
					end
				end
			end
		end
	end

	self.PreviousCFrame = currentCFrame

	if self.__Connection then
		for i,character in pairs(self._Touching) do
			if not table.find(characters, character) then
				table.remove(self._Touching, i)
				self.TouchEnded:Fire(character)
			end
		end

		for i,character in pairs(characters) do
			if not table.find(self._Touching, character) then						
				table.insert(self._Touching, character)
				self.TouchBegan:Fire(character)
			end
		end
	end

	if Settings.Debugging and self.__Debug and RunService:IsStudio() then
		task.spawn(function()
			local timestamp = os.clock()
			self.__Debugging = timestamp

			self.__Debug.Parent = workspace

			task.wait(.1)

			if self.__Debugging == timestamp then
				self.__Debugging = nil
				self.__Debug.Parent = nil
			end
		end)
	end

	if RunService:IsServer() 
		and not self.__Connection 
	then
		self:HitCharacters(characters)
	end

	return characters
end 

function Hitbox:Start(Timer: number?, Debounce: number?, ...)
	self._Args = {...}

	self.Started:Fire()

	self._Timer = Timer
	self._Debounce = Debounce

	self.PreviousCFrame = self.Focus.CFrame

	local timestamp = os.clock()
	self.__Timestamp = timestamp

	if Timer then
		task.delay(Timer, function()
			if self.__Timestamp == timestamp then
				self:Stop()
			end
		end)
	end

	if Settings.Debugging and self.__Debug and RunService:IsStudio() 
		and (RunService:IsClient() or not self.Client)
	then
		task.spawn(function()
			local _timestamp = os.clock()
			self.__Debugging = _timestamp

			self.__Debug.Parent = workspace
		end)
	end

	if RunService:IsServer() and self.Client then
		Event:FireClient(self.Client, "Start", self.Focus)

		if Debounce then
			task.spawn(function()
				while self.__Timestamp == timestamp do
					if #self._Touching > 0 then
						self:HitCharacters(self._Touching)
					end

					task.wait(Debounce)
				end
			end)
		end
	end

	if self.__Connection then self:Stop() end

	self.__Connection = RunService.Heartbeat:Connect(function()
		self:HitCharacters(self:Once())
	end)

	if Debounce then
		task.spawn(function()
			while self and self.__Timestamp == timestamp do task.wait(Debounce)
				self:Clear()
			end
		end)
	end
end

function Hitbox:Stop()

	if self.__Timestamp then
		self.__Timestamp = nil
		self._Trying = {}
		self._Touching = {}
		self.Stopped:Fire()
	end

	self._Timer = nil
	self._Debounce = nil

	if Settings.Debugging and self.__Debug and RunService:IsStudio() then
		task.spawn(function()
			local timestamp = os.clock()
			self.__Debugging = timestamp

			self.__Debug.Parent = nil
		end)
	end

	if RunService:IsServer() and self.Client then
		Event:FireClient(self.Client, "Stop", self.Focus)
	end

	if not self.__Connection then return end

	self.__Connection:Disconnect()
	self.__Connection = nil
end

function Hitbox:Clear()
	self._Characters = {}
end

function Hitbox:Destroy()
	Hitboxes[self.Focus] = nil

	if self.__Connection then self.__Connection:Disconnect() end
	if self.__Listen then self.__Listen:Disconnect() end
	if self.__Destroying then self.__Destroying:Disconnect() end
	if self.__Debug then self.__Debug:Destroy() end
	if self.__DebugSweep then self.__DebugSweep:Destroy() end -- add this

	self.Destroying:Fire()
end

local connection = nil
if RunService:IsServer() then
	connection = Event.OnServerEvent:Connect(function(Player: Player, Key: string, ...)
		if typeof(Key) == "string" then
			if Key == "Loaded" then
				Clients[Player] = true
			elseif Key == "Hit" then
				local args = {...}
				local Focus = args[1]
				local Characters = args[2]

				if typeof(Focus) == "Instance" and Focus:IsA("BasePart") and typeof(Characters) == "table" then
					local hitbox = Hitboxes[Focus]

					if hitbox
						and Player == hitbox.Client 
						and hitbox.__Timestamp
					then
						local characters = {}

						for i,character in pairs(hitbox._Touching) do
							if not table.find(Characters, character) then
								hitbox._Trying[character] = nil

								table.remove(hitbox._Touching, i)
								hitbox.TouchEnded:Fire(character)
							end
						end

						for i,character in pairs(Characters) do
							if typeof(character) == "Instance" and character:IsA("Model") 
								and character:FindFirstChildOfClass("Humanoid") and character.PrimaryPart
								and not hitbox._Trying[character]
							then
								hitbox._Trying[character] = true

								local function check()

									if not table.find(hitbox._Touching, character) 
										and hitbox:IsTouching(character)
									then
										hitbox._Trying[character] = nil

										table.insert(hitbox._Touching, character)
										hitbox.TouchBegan:Fire(character)

										return true
									end

									return false
								end

								local function hit()
									if not hitbox._Debounce
										and not table.find(hitbox._Characters, character) 
									then
										table.insert(hitbox._Characters, character)

										return true
									end

									return false
								end

								if check() then 
									if hit() then
										table.insert(characters, character)
									end
								else
									task.spawn(function()
										while hitbox.__Timestamp
											and hitbox._Trying[character] 
										do
											if check() then
												if hit() then
													hitbox.Hit:Fire({character}, table.unpack(hitbox._Args))
												end
											end

											task.wait()
										end
									end)
								end
							end
						end

						if #characters > 0 then
							hitbox.Hit:Fire(characters, table.unpack(hitbox._Args))
						end
					end
				end
			end
		end
	end)
elseif RunService:IsClient() then

	local function waitForTagged(tag: string, maxWait: number?)
		maxWait = maxWait or 10
		local startTime = tick()
		local part

		repeat
			part = game:GetService("CollectionService"):GetTagged(tag)[1]

			task.wait()
		until part or tick() - startTime > maxWait
		return part
	end

	Function.OnClientInvoke = (function(Key: string, Focus: BasePart, ...)
		if Focus == nil then return false end

		if Key == "New" then
			local args = {...}

			local position = args[1]
			local parameters = args[2]
			local debug = args[3]

			local params = OverlapParams.new()
			params.FilterDescendantsInstances = parameters.FilterDescendantsInstances or {}
			params.FilterType = parameters.FilterType or Enum.RaycastFilterType.Exclude
			params.RespectCanCollide = parameters.RespectCanCollide or false
			params.MaxParts = parameters.MaxParts or 0
			params.Tolerance = parameters.Tolerance or 0
			params.CollisionGroup = parameters.CollisionGroup or "Default"
			params.BruteForceAllSlow = parameters.BruteForceAllSlow or false

			local hitbox = Hitbox.New(Focus, params, nil, debug)
			if hitbox == nil then return false end

			local began = hitbox.TouchBegan:Connect(function()
				Event:FireServer("Hit", hitbox.Focus, hitbox._Touching)
			end)

			local ended = hitbox.TouchEnded:Connect(function()
				Event:FireServer("Hit", hitbox.Focus, hitbox._Touching)
			end)

			hitbox.Destroying:Once(function()
				began:Disconnect()
				ended:Disconnect()
			end)

			return true
		end

		return false
	end)

	connection = Event.OnClientEvent:Connect(function(Key: string, Focus: BasePart?, ...)
		if Focus == nil then return end

		if typeof(Key) == "string" and typeof(Focus) == "Instance" and Focus:IsA("BasePart") then			
			if Key == "Start" 
				and Hitboxes[Focus]
			then
				local hitbox = Hitboxes[Focus]
				hitbox:Start()
			elseif Key == "Stop" 
				and Hitboxes[Focus]
			then
				local hitbox = Hitboxes[Focus]
				hitbox:Stop()
			end
		end
	end)
end

if RunService:IsServer() then
	repeat task.wait() until connection
	task.wait()
	script:AddTag("__loaded")
elseif RunService:IsClient() then
	repeat task.wait() until connection and script:HasTag("__loaded")
	task.wait()
	Event:FireServer("Loaded")
end

return Hitbox :: Hitbox
1 Like