function getLimbs(S,k)local F={}local w={}local W={}local function p(S,z)if S.Name~="\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116"then local k=getLimbType(S.Name)F[k]=F[k]or{}table.insert(F[k],S)if k~=z then W[k]=W[k]or{}if z then W[k][z]=true end table.insert(w,{Part=S,Type=k})z=k end end for F,w in pairs(S:GetChildren())do if w:isA("\065\116\116\097\099\104\109\101\110\116")and k[w.Name]then local F=k[w.Name].Attachment1.Parent if F and F~=S then p(F,z)end end end end p(S)return F,w,W end function createNoCollision(S,k)local F=Instance.new("\078\111\067\111\108\108\105\115\105\111\110\067\111\110\115\116\114\097\105\110\116")F.Name=S.Name..("\060-\062"..k.Name)F.Part0=S F.Part1=k return F end return function(S,k)local F=Instance.new("\070\111\108\100\101\114")F.Name="\078\111\067\111\108\108\105\115\105\111\110\067\111\110\115\116\114\097\105\110\116\115"local w,W,p=getLimbs(k,S)for S=1,#W,1 do for k=S+1,#W,1 do local w,z=W[S].Type,W[k].Type if not(p[w][z]or p[z][w])then(createNoCollision(W[S].Part,W[k].Part)).Parent=F end end end for S,k in pairs(w)do for S,W in pairs(p[S])do for S,w in pairs(w[S])do for S,k in pairs(k)do(createNoCollision(k,w)).Parent=F end end end end return F end
--[[
	We need a lot of information to determine which collisions to filter, but they are all
	condensed into one place to minimize redundant loops in case someone is spam-spawning
	characters with hundreds of limbs. Returns the following information:
	
	limbs:
	{
		["Arm"] = { LeftUpperArm, LeftLowerArm, LeftHand, RightUpperArm, RightLowerArm, RightHand},
		["Head"] = { Head },
		["Torso"] = { LowerTorso, UpperTorso },
		...	
	}
	
	limbRootParts:
	{ {Part=Head, Type=Head}, {Part=LeftUpperArm, Type="Arm"}, {Part=RightUpperArm, Type="Arm"}, ... }
	
	limbParents:
	{
		["Arm"] = { "Torso" },
		["Head"] = { "Torso" },
		["Torso"] = {},
		...	
	}
--]]
function getLimbs(characterRoot, attachmentMap)
	local limbs = {}
	local limbRootParts = {}
	local limbParents = {}

	local function parsePart(part, lastLimb)
		if part.Name ~= "HumanoidRootPart" then
			local limbType = getLimbType(part.Name)
			limbs[limbType] = limbs[limbType] or {}

			table.insert(limbs[limbType], part)

			--//local limbData = limbs[limbType]
			if limbType ~= lastLimb then
				limbParents[limbType] = limbParents[limbType] or {}
				if lastLimb then
					limbParents[limbType][lastLimb] = true
				end

				table.insert(limbRootParts, { Part = part, Type = limbType })
				lastLimb = limbType
			end
		end

		for _, v in pairs(part:GetChildren()) do
			if v:isA("Attachment") and attachmentMap[v.Name] then
				local part1 = attachmentMap[v.Name].Attachment1.Parent
				if part1 and part1 ~= part then
					parsePart(part1, lastLimb)
				end
			end
		end
	end
	parsePart(characterRoot)

	return limbs, limbRootParts, limbParents
end

function createNoCollision(part0, part1)
	local noCollision = Instance.new("NoCollisionConstraint")
	noCollision.Name = part0.Name .. "<->" .. part1.Name
	noCollision.Part0 = part0
	noCollision.Part1 = part1

	return noCollision
end

return function(attachmentMap, characterRoot)
	local noCollisionConstraints = Instance.new("Folder")
	noCollisionConstraints.Name = "NoCollisionConstraints"

	local limbs, limbRootParts, limbParents = getLimbs(characterRoot, attachmentMap)

	--[[
		Disable collisions between all limb roots (e.g. left/right shoulders, head, etc) unless
		one of them is a parent of the other (handled in next step). This is to ensure limbs
		maintain free range of motion and we don't have issues like
			- Large shoulders clipping with the head and all of them being unable to move
			- Legs being stuck together because rotating would cause the collision boxes to intersect
	--]]
	for i = 1, #limbRootParts do
		for j = i + 1, #limbRootParts do
			local limbType0, limbType1 = limbRootParts[i].Type, limbRootParts[j].Type

			if not (limbParents[limbType0][limbType1] or limbParents[limbType1][limbType0]) then
				createNoCollision(limbRootParts[i].Part, limbRootParts[j].Part).Parent = noCollisionConstraints
			end
		end
	end

	--[[
		Disable collisions between limbs and their parent limbs. This is mostly to address
		bundle torsos having insane hitboxes that touch more than just limb roots
	--]]
	for limbType, parts in pairs(limbs) do
		for parentLimbType, _ in pairs(limbParents[limbType]) do
			for _, part2 in pairs(limbs[parentLimbType]) do
				for _, part in pairs(parts) do
					createNoCollision(part, part2).Parent = noCollisionConstraints
				end
			end
		end
	end

	return noCollisionConstraints
end
