
function window_placer(x, y, placer)
	local lx, ly = math.floor(x), math.floor(y)
	local nearest, nearest_dist = nil, math.huge
	for _, dir in ipairs(direction.ALL) do
		if level.get_wall(lx, ly, dir) ~= "none" then
			local ox, oy = direction.offset(dir)
			local dx = (lx + 0.5 + ox * 0.5) - x
			local dy = (ly + 0.5 + oy * 0.5) - y
			local distance = math.sqrt(dx*dx + dy*dy)
			if distance < nearest_dist then
				nearest = dir
				nearest_dist = distance
			end
		end
	end

	if nearest ~= nil then
		local ox, oy = direction.offset(nearest)

		-- Flip if the door isn't facing outwards
		-- (useful for rooms with borders)
		if placer:in_bounds(lx + ox, ly + oy) and placer:in_bounds(lx + ox * 2, ly + oy * 2) then
			lx, ly = lx + ox, ly + oy
			nearest = direction.reverse(nearest)
			ox, oy = direction.offset(nearest)
		end

		-- Flip if targetting outside
		if not placer:in_bounds(lx, ly) and placer:in_bounds(lx + ox, ly + oy) then
			lx, ly = lx + ox, ly + oy
			nearest = direction.reverse(nearest)
			ox, oy = direction.offset(nearest)
		end

		local valid = level.get_wall(lx, ly, nearest) == "wall"
		if not valid then
			placer:fail("Blocked by window/door")
			return
		end
		placer:place_window(lx, ly, nearest, "walls/simple")
		-- Make sure the window is part of the room
		if not placer:in_bounds(lx, ly) and not placer:in_bounds(lx + ox, ly + oy) then
			placer:fail("Must be attached to the room")
		end
		if placer:in_bounds(lx, ly) then
			placer:blocks_tile(lx, ly)
		end
		if placer:in_bounds(lx + ox, ly + oy) then
			placer:blocks_tile(lx + ox, ly + oy)
		end

	else
		placer:fail("No wall to attach to")
	end
    return 0
end

function projector_placer(x, y, placer)
	local lx, ly = math.floor(x), math.floor(y)
	local nearest, nearest_dist = nil, math.huge
	for _, dir in ipairs(direction.ALL) do
		if level.get_wall(lx, ly, dir) ~= "none" then
			local ox, oy = direction.offset(dir)
			local dx = (lx + 0.5 + ox * 0.5) - x
			local dy = (ly + 0.5 + oy * 0.5) - y
			local distance = math.sqrt(dx*dx + dy*dy)
			if distance < nearest_dist then
				nearest = dir
				nearest_dist = distance
			end
		end
	end

	if nearest ~= nil then
		local rotation = 0.0
		if nearest == "north" then
			rotation = math.pi
		elseif nearest == "east" then
			rotation = math.pi * 1.5
		elseif nearest == "west" then
			rotation = math.pi / 2.0
		end
		local ox, oy = direction.offset(nearest)
		local px, py = lx + 0.5 - ox * (1.2/16), ly + 0.5 - oy * (1.2/16)
		placer:static_model("base:teach_objs/projector", px, 0.0, py, rotation)
		placer:placement_bound(
			px - 1.5 + (-ox * 0.5 + 0.5) * math.abs(ox),
			py - 1.5 + (-oy * 0.5 + 0.5) * math.abs(oy),
			3.0 - math.abs(ox), 3.0 - math.abs(oy)
		)
		placer:selection_bound(px - 0.75, 0.0, py - 0.75,  1.5, 0.5, 1.5)
		-- Ensure the projector has 3 walls behind it
		-- to prevent hanging over the edge if placed at
		-- an edge
		for i = -1, 1 do
			if level.get_wall(lx + i * math.abs(oy), ly + i * math.abs(ox), nearest) == "none" then
				placer:fail("Whiteboard must be completely backed by walls")
				break
			end
			-- Make sure the object is inside the room
			if not placer:in_bounds(lx + i * math.abs(oy), ly + i * math.abs(ox)) then
				placer:fail("Must be inside the room")
			end
		end
	else
		placer:static_model("base:teach_objs/projector", lx + 0.5, 0.0, ly + 0.5, 0.0)
		placer:fail("No wall to attach to")
	end
    return 0
end

function board_placer(x, y, placer)
	local lx, ly = math.floor(x), math.floor(y)
	local nearest, nearest_dist = nil, math.huge
	for _, dir in ipairs(direction.ALL) do
		if level.get_wall(lx, ly, dir) ~= "none" then
			local ox, oy = direction.offset(dir)
			local dx = (lx + 0.5 + ox * 0.5) - x
			local dy = (ly + 0.5 + oy * 0.5) - y
			local distance = math.sqrt(dx*dx + dy*dy)
			if distance < nearest_dist then
				nearest = dir
				nearest_dist = distance
			end
		end
	end

	if nearest ~= nil then
		local rotation = 0.0
		if nearest == "north" then
			rotation = math.pi
		elseif nearest == "east" then
			rotation = math.pi * 1.5
		elseif nearest == "west" then
			rotation = math.pi / 2.0
		end
		local ox, oy = direction.offset(nearest)
		local px, py = lx + 0.5 - ox * 0, ly + 0.5 - oy * 0
		placer:static_model("base:teach_objs/grid_board", px + ox * 0.4, 0.0, py + oy * 0.4, rotation)
		placer:placement_bound(
			lx, ly,
			1.0, 1.0
		)
		placer:selection_bound(lx, 0.0, lx,  1.0, 0.5, 1.0)

		if level.get_wall(lx, ly, nearest) == "none" then
			placer:fail("Whiteboard must be completely backed by walls")
		end
		-- Make sure the object is inside the room
		if not placer:in_bounds(lx, ly) then
			placer:fail("Must be inside the room")
		end
	else
		placer:static_model("base:teach_objs/grid_board", lx + 0.5, 0.0, ly + 0.5, 0.0)
		placer:fail("No wall to attach to")
	end
    return 0
end

function door_placer(x, y, placer, version)
	if version == 0 then
		-- Doors could be placed on rooms that didn't belong to them
		-- from the building allowing for invalid state, this removes them
		placer:remove_on_error()
	end
	local mdl = placer.parameters['model']
	local texture = placer.parameters['texture']

	local lx, ly = math.floor(x), math.floor(y)
	local nearest, nearest_dist = nil, math.huge

	function is_door_valid(ox, oy, nearest)
		local lx, ly = lx, ly
		-- Flip if the door isn't facing outwards
		-- (useful for rooms with borders)
		if placer:in_bounds(lx + ox, ly + oy) and placer:in_bounds(lx + ox * 2, ly + oy * 2) then
			lx, ly = lx + ox, ly + oy
			nearest = direction.reverse(nearest)
			ox, oy = direction.offset(nearest)
		end

		-- Flip if targetting outside
		if not placer:in_bounds(lx, ly) and placer:in_bounds(lx + ox, ly + oy) then
			lx, ly = lx + ox, ly + oy
			nearest = direction.reverse(nearest)
			ox, oy = direction.offset(nearest)
		end

		local valid = level.get_wall(lx, ly, nearest) == "wall"
		if not valid then
			return "Blocked by " .. level.get_wall(lx, ly, nearest), lx, ly, ox, oy, nearest
		end

		if level.get_room_type_at(lx, ly) ~= "base:building"
			and level.get_room_type_at(lx + ox, ly + oy) ~= "base:building" then
			return "Must be joining to a building", lx, ly, ox, oy, nearest
		end

		if placer.room_key == "base:building" then
			if not placer:owns_tile(lx, ly) or not placer:owns_tile(lx + ox, ly + oy) then
				return "Can't place on room from outside", lx, ly, ox, oy, nearest
			end
		end

		-- Make sure the door is part of the room
		if not placer:in_bounds(lx, ly) then
			return "Must be attached to the room", lx, ly, ox, oy, nearest
		end

		return true, lx, ly, ox, oy, nearest
	end

	for try = 1, 2 do
		for _, dir in ipairs(direction.ALL) do
			if level.get_wall(lx, ly, dir) ~= "none" then
				local ox, oy = direction.offset(dir)
				local dx = (lx + 0.5 + ox * 0.5) - x
				local dy = (ly + 0.5 + oy * 0.5) - y

				local skip = false
				local valid = is_door_valid(ox, oy, dir)
				if valid ~= true then
					if try == 1 then
						skip = true
					end
				end

				local distance = math.sqrt(dx*dx + dy*dy)
				if distance < nearest_dist and not skip then
					nearest = dir
					nearest_dist = distance
				end
			end
		end
		if nearest ~= nil then
			break
		end
	end

	if nearest ~= nil then
		local ox, oy = direction.offset(nearest)

		local err, lx, ly, ox, oy, nearest = is_door_valid(ox, oy, nearest)

		if err ~= true then
			placer:fail(err)
		end

		local rotation = 0.0
		if nearest == "north" then
			rotation = math.pi
		elseif nearest == "east" then
			rotation = math.pi * 1.5
		elseif nearest == "west" then
			rotation = math.pi / 2.0
		end
		placer:mark_door(lx, ly, nearest)
		if texture == nil then
			placer:animated_model(mdl, "closed", lx + 0.5 + ox * 0.5, 0.0, ly + 0.5 + oy * 0.5, rotation)
		else
			placer:animated_model_tex(mdl, texture, "closed", lx + 0.5 + ox * 0.5, 0.0, ly + 0.5 + oy * 0.5, rotation)
		end

		-- Block placing in front of the door
		placer:placement_bound(lx, ly, 1.0, 1.0)
		placer:placement_bound(lx + ox, ly + oy, 1.0, 1.0)
		if placer:in_bounds(lx, ly) then
			placer:blocks_tile(lx, ly)
		end
		if placer:in_bounds(lx + ox, ly + oy) then
			placer:blocks_tile(lx + ox, ly + oy)
		end

		if level.get_tile(lx, ly).has_property('building_edge') then
			placer:set_tile(lx, ly, "external/cobbles")
		end

		if level.get_tile(lx + ox, ly + oy).has_property('building_edge') then
			placer:set_tile(lx + ox, ly + oy, "external/cobbles")
		end
	else
		placer:animated_model("base:doors/basic", "closed", lx + 0.5, 0.0, ly + 0.5, 0.0)
		placer:fail("No wall to attach to")
	end
    return 1
end