function Grid._onAddGridObjectRejected(o,S,G,E,F)o.tempContentsManager:removeByName(S..("\124"..tostring(G)))end local function S(S)local G=S:FindFirstChild("\087\101\108\100\067\111\110\115\116\114\097\105\110\116")while G do G:Destroy()G=S:FindFirstChild("\087\101\108\100\067\111\110\115\116\114\097\105\110\116")end end function Grid._onDestroyObject(E,G)if G:IsDescendantOf(E.contentsManager.folder)then local F=G:GetConnectedParts()E.contentsManager:removeByName(G.Name)local o={G}for G,F in ipairs(F)do if F and(o[F]==nil and not F:IsGrounded())then local G=F:GetConnectedParts(true)if not E.physicallySimulateDestruction then for S,G in ipairs(G)do if not SceneryManager.isScenery(G)then E.contentsManager:removeByName(G.Name)end end if not SceneryManager.isScenery(F)then E.contentsManager:removeByName(F.Name)end else local F=false E:_performFadeOutAnimation(G)for d,e in ipairs(G)do if o[e]==nil and not SceneryManager.isScenery(e)then E.contentsManager:removeByName(e.Name,nil,true)PhysicsService:SetPartCollisionGroup(e,"\068\101\102\097\117\108\116")o[e]=true local d={}table.insert(d,e.Touched:Connect(function(E)if SceneryManager.isScenery(E)and F==false then F=true for G,E in ipairs(G)do S(E)end for S,G in ipairs(d)do G:Disconnect()end d={}end end))end end end end end end end function Grid._performFadeOutAnimation(G,S)for S,G in ipairs(S)do CollectionService:AddTag(G,"\087\101\097\112\111\110\115\083\121\115\116\101\109\073\103\110\111\114\101")local E=TweenService:Create(G,TweenInfo.new(1,Enum.EasingStyle.Linear,Enum.EasingDirection.Out,0,false,5),{Transparency=1})E.Completed:Connect(function()G:Destroy()end)E:Play()end end function Grid.setPlacementCFrame(o,S,G,E,F)if S==nil or S.Position==nil or S.LookVector==nil or G==nil then o:_destroyPreview()return end local d={Players,game.Workspace.CurrentCamera}if o.preview and o.preview.part then table.insert(d,o.preview.part)end local e,w=penetrateCast(Ray.new(S.Position,(S.LookVector.Unit*100)*Grid.CELL_DIMENSIONS.Magnitude),d)local D=math.min((w-S.Position).Magnitude,100*Grid.CELL_DIMENSIONS.Magnitude)o:_updatePreview(S,D,G,E,F)end function Grid.placePreview(S)if S.preview and(S.preview.objectType~=nil and(S.preview.cell~=nil and S.preview.rotation~=nil))then local G=S.tempContentsManager:add(S.preview.objectType,S.preview.cell.id)if G then AddGridObject:FireServer(S.preview.objectType.name,S.preview.cell.id)end S:_destroyPreview()end end function Grid._destroyPreview(S)if S.tempContentsManager~=nil and S.preview~=nil then if S.preview.part then S.preview.part.Transparency=1 end S.preview.cell=nil S.preview.rotation=nil S.preview.face=nil end end function Grid.renderPreview(E,S,G)S:render(E.tempContentsManager,G,true)end function Grid.canPlace(E,S,G)return E.tempContentsManager:canPlace(S,G.id)end local function G(S)local G=S.LookVector.Unit local E=math.max(math.abs(G.X),math.abs(G.Y),math.abs(G.Z))if math.abs(G.X)==E then return Enum.Axis.X,math.sign(G.X)elseif math.abs(G.Z)==E then return Enum.Axis.Z,math.sign(G.Z)else return Enum.Axis.Y,math.sign(G.Y)end end local function E(S,G)local E=G.id-S.id return math.abs(E.X)<=1 and(math.abs(E.Y)<=1 and math.abs(E.Z)<=1)end function Grid._getCandidateCells(o,S,F)local d=Cell.newFromWorldVector(o,S.Position)local e=Cell.newFromWorldVector(o,F.Position)local w,D=G(F)local K={}local Y={Enum.Axis.X,Enum.Axis.Y;Enum.Axis.Z}for S,G in ipairs(Y)do if G~=w then local S=Vector3.FromAxis(G)if G~=Enum.Axis.Y then if S:Dot(F.LookVector.Unit)>.5 then local G=Cell.new(o,e.id+S)if E(d,G)then table.insert(K,Cell.new(o,e.id+S))end elseif(-1*S):Dot(F.LookVector.Unit)>.5 then local G=Cell.new(o,e.id+-1*S)if E(d,G)then table.insert(K,G)end end else local G=Cell.new(o,e.id+S)if E(d,G)then table.insert(K,Cell.new(o,e.id+S))end G=Cell.new(o,e.id+-1*S)if E(d,G)then table.insert(K,G)end end end end if e.id~=d.id then table.insert(K,e)end local V=F.LookVector.Unit local M=math.deg(math.atan(V.Y/math.sqrt(V.X*V.X+V.Z*V.Z)))if math.abs(M)>65 then if e.id~=d.id then local S=Cell.new(o,e.id+(Vector3.FromAxis(w)*-1)*D)if E(d,S)then table.insert(K,S)end else table.insert(K,e)end end table.sort(K,function(G,E)return(S.Position-G.center).Magnitude<(S.Position-E.center).Magnitude end)return K end function Grid._updatePreview(d,S,G,E,F,o)local e=d:_getCandidateCells(S,CFrame.new(S.Position+math.min(Grid.CELL_DIMENSIONS.X,G)*S.LookVector.Unit,S.Position+2*(math.min(Grid.CELL_DIMENSIONS.X,G)*S.LookVector.Unit)))local w if math.sign(S.LookVector.Z)>0 and math.abs(S.LookVector.Z)>=math.abs(S.LookVector.X)then w=0 elseif math.sign(S.LookVector.X)>0 and math.abs(S.LookVector.Z)<=math.abs(S.LookVector.X)then w=90 elseif math.sign(S.LookVector.Z)<0 and math.abs(S.LookVector.Z)>=math.abs(S.LookVector.X)then w=180 elseif math.sign(S.LookVector.X)<0 and math.abs(S.LookVector.Z)<=math.abs(S.LookVector.X)then w=270 end if BitOps.getVerticalFaceFromOccupancy(ObjectTypeConfigurations[E].OCCUPANCY)then F=0 end local D=(w+F)%360 local K=E..("\036"..(D..("\036"..o)))local Y=d.objectTypes[K]if Y==nil then error("\084\114\121\105\110\103 \116\111 \112\108\097\099\101 \112\114\101\118\105\101\119 \111\102 \117\110\107\110\111\119\110 \111\098\106\101\099\116 \116\121\112\101 "..K)end for S,G in ipairs(e)do if d:canPlace(Y,G)then d:renderPreview(Y,G)return end end d:_destroyPreview()end function Grid.destroy(S)S:_destroyPreview()if S.contentsFolder then S.contentsFolder:Destroy()S.contentsFolder=nil end if S.tempContentsFolder then S.tempContentsFolder:Destroy()S.tempContentsFolder=nil end for S,G in pairs(S.connections)do G:Disconnect()end S.connections=nil end return Grid

function Grid:_onAddGridObjectRejected(objectTypeName, cellId, rotation, face)
	self.tempContentsManager:removeByName(objectTypeName .. "|" .. tostring(cellId))
end

local function destroyAllWelds(part)
	local weld = part:FindFirstChild("WeldConstraint")
	while weld do
		weld:Destroy()
		weld = part:FindFirstChild("WeldConstraint")
	end
end

function Grid:_onDestroyObject(part)
	if part:IsDescendantOf(self.contentsManager.folder) then
		local connectedParts = part:GetConnectedParts()
		self.contentsManager:removeByName(part.Name)

		local removedPartSet = { part }
		for _, connectedPart in ipairs(connectedParts) do
			if connectedPart and removedPartSet[connectedPart] == nil and (not connectedPart:IsGrounded()) then
				local recursivelyConnectedParts = connectedPart:GetConnectedParts(true)
				if not self.physicallySimulateDestruction then
					for _, recursivelyConnectedPart in ipairs(recursivelyConnectedParts) do
						if not SceneryManager.isScenery(recursivelyConnectedPart) then
							self.contentsManager:removeByName(recursivelyConnectedPart.Name)
						end
					end

					if not SceneryManager.isScenery(connectedPart) then
						self.contentsManager:removeByName(connectedPart.Name)
					end
				else
					local hasHitGround = false
					self:_performFadeOutAnimation(recursivelyConnectedParts)
					for _, recursivelyConnectedPart in ipairs(recursivelyConnectedParts) do
						if removedPartSet[recursivelyConnectedPart] == nil and not SceneryManager.isScenery(recursivelyConnectedPart) then
							self.contentsManager:removeByName(recursivelyConnectedPart.Name, nil, true)
							PhysicsService:SetPartCollisionGroup(recursivelyConnectedPart, "Default")
							removedPartSet[recursivelyConnectedPart] = true
							local connections = {}
							table.insert(connections, recursivelyConnectedPart.Touched:Connect(function(otherPart)
								if SceneryManager.isScenery(otherPart) and hasHitGround == false then
									hasHitGround = true
									for _, partToDestroy in ipairs(recursivelyConnectedParts) do
										destroyAllWelds(partToDestroy)
									end
									for _, connection in ipairs(connections) do
										connection:Disconnect()
									end
									connections = {}
								end
							end))
						end
					end
				end
			end
		end
	end
end

function Grid:_performFadeOutAnimation(recursivelyConnectedParts)
	for _, partToFade in ipairs(recursivelyConnectedParts) do
		CollectionService:AddTag(partToFade, "WeaponsSystemIgnore")
		local fadeOutTween = TweenService:Create(
			partToFade,
			TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, 0, false, 5),
			{ Transparency = 1 }
		)
		fadeOutTween.Completed:Connect(function()
			partToFade:Destroy()
		end)
		fadeOutTween:Play()
	end
end

function Grid:setPlacementCFrame(cframe, simpleObjectType, rotation, faceRotation)
	if cframe == nil or cframe.Position == nil or cframe.LookVector == nil or simpleObjectType == nil then
		self:_destroyPreview()
		return
	end

	local ignoreList = {
		Players,
		game.Workspace.CurrentCamera,
	}
	if self.preview and self.preview.part then
		table.insert(ignoreList, self.preview.part)
	end

	local _, hitPoint = penetrateCast(Ray.new(cframe.Position, cframe.LookVector.Unit * 100 * Grid.CELL_DIMENSIONS.Magnitude),
		ignoreList
	)
	local rayLength = math.min((hitPoint - cframe.Position).Magnitude, 100 * Grid.CELL_DIMENSIONS.Magnitude)

	self:_updatePreview(cframe, rayLength, simpleObjectType, rotation, faceRotation)
end

function Grid:placePreview()
	if self.preview and self.preview.objectType ~= nil and self.preview.cell ~= nil and self.preview.rotation ~= nil then
		local placed = self.tempContentsManager:add(self.preview.objectType, self.preview.cell.id)
		if placed then
			AddGridObject:FireServer(self.preview.objectType.name, self.preview.cell.id)
		end

		self:_destroyPreview()
	end
end

function Grid:_destroyPreview()
	if self.tempContentsManager ~= nil and self.preview ~= nil then
		if self.preview.part then
			self.preview.part.Transparency = 1
		end

		self.preview.cell = nil
		self.preview.rotation = nil
		self.preview.face = nil
	end
end

function Grid:renderPreview(objectType, cell)
	objectType:render(self.tempContentsManager, cell, true)
end

function Grid:canPlace(objectType, cell)
	return self.tempContentsManager:canPlace(objectType, cell.id)
end

local function getPrimaryAxisAndDirection(cframe)
	local lookUnitVector = cframe.LookVector.Unit
	local lookVectorMaxComponent = math.max(math.abs(lookUnitVector.X), math.abs(lookUnitVector.Y), math.abs(lookUnitVector.Z))

	if math.abs(lookUnitVector.X) == lookVectorMaxComponent then
		return Enum.Axis.X, math.sign(lookUnitVector.X)
	elseif math.abs(lookUnitVector.Z) == lookVectorMaxComponent then
		return Enum.Axis.Z, math.sign(lookUnitVector.Z)
	else
		return Enum.Axis.Y, math.sign(lookUnitVector.Y)
	end
end

local function cellWithinRange(originCell, targetCell)
	local cellDist = targetCell.id - originCell.id
	return math.abs(cellDist.X) <= 1 and math.abs(cellDist.Y) <= 1 and math.abs(cellDist.Z) <= 1
end

function Grid:_getCandidateCells(originCFrame, targetCFrame)
	local originCell = Cell.newFromWorldVector(self, originCFrame.Position)
	local targetCell = Cell.newFromWorldVector(self, targetCFrame.Position)
	local primaryLookAxis, axisDirection = getPrimaryAxisAndDirection(targetCFrame)

	local candidateCells = {}

	local axes = {
		Enum.Axis.X,
		Enum.Axis.Y,
		Enum.Axis.Z,
	}

	for _, axis in ipairs(axes) do
		if axis ~= primaryLookAxis then
			local axisAsVec3 = Vector3.FromAxis(axis)
			if axis ~= Enum.Axis.Y then
				if axisAsVec3:Dot(targetCFrame.LookVector.Unit) > 0.5 then
					local candidateCell = Cell.new(self, targetCell.id + axisAsVec3)
					if cellWithinRange(originCell, candidateCell) then
						table.insert(candidateCells, Cell.new(self, targetCell.id + axisAsVec3))
					end
				elseif (-1 * axisAsVec3):Dot(targetCFrame.LookVector.Unit) > 0.5 then
					local candidateCell = Cell.new(self, targetCell.id + -1 * axisAsVec3)
					if cellWithinRange(originCell, candidateCell) then
						table.insert(candidateCells, candidateCell)
					end
				end
			else
				local candidateCell = Cell.new(self, targetCell.id + axisAsVec3)
				if cellWithinRange(originCell, candidateCell) then
					table.insert(candidateCells, Cell.new(self, targetCell.id + axisAsVec3))
				end

				candidateCell = Cell.new(self, targetCell.id + -1 * axisAsVec3)
				if cellWithinRange(originCell, candidateCell) then
					table.insert(candidateCells, candidateCell)
				end
			end
		end
	end

	if targetCell.id ~= originCell.id then
		table.insert(candidateCells, targetCell)
	end

	-- TODO: Try to get the right feel without conditional logic on the Y angle
	local lookVector = targetCFrame.LookVector.Unit
	local lookVectorYAngle = math.deg(math.atan(lookVector.Y / math.sqrt(lookVector.X * lookVector.X + lookVector.Z * lookVector.Z)))
	if math.abs(lookVectorYAngle) > 65 then
		if targetCell.id ~= originCell.id then
			local candidateCell = Cell.new(self, targetCell.id + Vector3.FromAxis(primaryLookAxis) * -1 * axisDirection)
			if cellWithinRange(originCell, candidateCell) then
				table.insert(candidateCells, candidateCell)
			end
		else
			table.insert(candidateCells, targetCell)
		end
	end

	table.sort(candidateCells, function(cell1, cell2)
		return (originCFrame.Position - cell1.center).Magnitude < (originCFrame.Position - cell2.center).Magnitude
	end)

	return candidateCells
end

function Grid:_updatePreview(cframe, maxDist, simpleObjectType, rotation, faceRotation)
	local candidateCells = self:_getCandidateCells(cframe, CFrame.new(
		cframe.Position + math.min(Grid.CELL_DIMENSIONS.X, maxDist) * cframe.LookVector.Unit,
		cframe.Position + 2 * (math.min(Grid.CELL_DIMENSIONS.X, maxDist) * cframe.LookVector.Unit)
	))

	local lookRotation
	if math.sign(cframe.LookVector.Z) > 0 and math.abs(cframe.LookVector.Z) >= math.abs(cframe.LookVector.X) then
		lookRotation = 0
	elseif math.sign(cframe.LookVector.X) > 0 and math.abs(cframe.LookVector.Z) <= math.abs(cframe.LookVector.X) then
		lookRotation = 90
	elseif math.sign(cframe.LookVector.Z) < 0 and math.abs(cframe.LookVector.Z) >= math.abs(cframe.LookVector.X) then
		lookRotation = 180
	elseif math.sign(cframe.LookVector.X) < 0 and math.abs(cframe.LookVector.Z) <= math.abs(cframe.LookVector.X) then
		lookRotation = 270
	end

	if BitOps.getVerticalFaceFromOccupancy(ObjectTypeConfigurations[simpleObjectType].OCCUPANCY) then
		rotation = 0
	end

	local actualRotation = (lookRotation + rotation) % 360

	local objectTypeName = simpleObjectType .. "$" .. actualRotation .. "$" .. faceRotation
	local objectType = self.objectTypes[objectTypeName]
	if objectType == nil then
		error("Trying to place preview of unknown object type " .. objectTypeName)
	end

	for _, candidateCell in ipairs(candidateCells) do
		if self:canPlace(objectType, candidateCell) then
			self:renderPreview(objectType, candidateCell)
			return
		end
	end

	self:_destroyPreview()
end

function Grid:destroy()
	self:_destroyPreview()

	if self.contentsFolder then
		self.contentsFolder:Destroy()
		self.contentsFolder = nil
	end

	if self.tempContentsFolder then
		self.tempContentsFolder:Destroy()
		self.tempContentsFolder = nil
	end

	for _, connection in pairs(self.connections) do
		connection:Disconnect()
	end

	self.connections = nil
end

return Grid
