function getLimbs(V,U)local f={}local g={}local E={}local function j(V,k)if V.Name~="\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116"then local U=getLimbType(V.Name)f[U]=f[U]or{}table.insert(f[U],V)local j=f[U]if U~=k then E[U]=E[U]or{}if k then E[U][k]=true end table.insert(g,{Part=V;Type=U})k=U end end for f,g in pairs(V:GetChildren())do if g:isA("\065\116\116\097\099\104\109\101\110\116")and U[g.Name]then local f=U[g.Name].Attachment1.Parent if f and f~=V then j(f,k)end end end end j(V)return f,g,E end function createNoCollision(V,U)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=V.Name..("\060-\062"..U.Name)f.Part0=V f.Part1=U return f end return function(V,U)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 g,E,j=getLimbs(U,V)for V=1,#E,1 do for U=V+1,#E,1 do local g,k=E[V].Type,E[U].Type if not(j[g][k]or j[k][g])then(createNoCollision(E[V].Part,E[U].Part)).Parent=f end end end for V,U in pairs(g)do for V,E in pairs(j[V])do for V,g in pairs(g[V])do for V,U in pairs(U)do(createNoCollision(U,g)).Parent=f end end end end return f end

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
