code:
local n1 = { name = "ignore"}
local n8 = { name = "air", force_place=true }

local n3 = { name = "df_underworld_items:slade_brick", force_place=true }
local n4 = { name = "df_underworld_items:slade_wall", force_place=true }
local n5 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 1, force_place=true }
local n6 = { name = df_dependencies.node_name_stair_slade_brick, force_place=true }
local n7 = { name = df_dependencies.node_name_stair_outer_slade_brick, force_place=true }
local n9 = { name = df_dependencies.node_name_slab_slade_brick, param2 = 23, force_place=true }
local n10 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 1, force_place=true }
local n11 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 3, force_place=true }
local n14 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 23, force_place=true }
local n15 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 2, force_place=true }
local n16 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 2, force_place=true }
local n17 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 3, force_place=true }

-- foundation nodes
local n2 = { name = "df_underworld_items:slade_brick", force_place=false, place_on_condition=mapgen_helper.buildable_to  }
local n12 = { name = "df_underworld_items:slade_wall", force_place=false, place_on_condition=mapgen_helper.buildable_to  }
local n13 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 1, force_place=false, place_on_condition=mapgen_helper.buildable_to  }

return {
	name = "df_caverns:medium_building",
	size = {x = 15, y = 12, z = 8},
	center_pos = {x = 10, y = 6, z = 3},
	data = {
		n1, n1, n1, n1, n1, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1,
		n1, n1, n1, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1,
		n1, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1,
		n1, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n1, n2,
		n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n1, n3, n3, n3,
		n3, n3, n3, n3, n3, n1, n1, n1, n1, n1, n1, n1, n3, n4, n4, n4, n4,
		n4, n4, n3, n1, n1, n1, n1, n1, n1, n1, n3, n4, n4, n4, n4, n4, n4,
		n3, n1, n1, n1, n1, n1, n1, n1, n3, n3, n3, n3, n3, n3, n3, n3, n1,
		n1, n1, n1, n1, n1, n1, n3, n4, n4, n4, n4, n4, n4, n3, n1, n1, n1,
		n1, n1, n1, n1, n3, n4, n4, n4, n4, n4, n4, n3, n1, n1, n1, n1, n1,
		n1, n1, n5, n6, n6, n6, n6, n6, n6, n7, n1, n1, n1, n1, n1, n1, n1,
		n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n1, n2, n2,
		n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n1, n2, n2, n2, n2,
		n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n1, n2, n2, n2, n2, n2, n2,
		n2, n2, n1, n1, n1, n1, n1, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2,
		n1, n1, n1, n1, n1, n1, n1, n3, n3, n3, n3, n3, n3, n3, n3, n1, n1,
		n1, n1, n1, n1, n8, n4, n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1,
		n1, n1, n8, n4, n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1,
		n1, n3, n9, n9, n9, n9, n9, n9, n3, n1, n1, n1, n1, n1, n1, n1, n4,
		n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1, n1, n4, n8, n8,
		n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1, n1, n10, n9, n9, n9, n9,
		n9, n9, n11, n12, n12, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2,
		n2, n2, n1, n12, n12, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2,
		n1, n1, n12, n12, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1,
		n1, n12, n12, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1,
		n12, n12, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1,
		n12, n12, n3, n3, n3, n3, n3, n3, n3, n3, n1, n1, n1, n1, n1, n1,
		n12, n3, n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1, n8, n3,
		n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1, n1, n3, n9, n9,
		n9, n9, n9, n9, n3, n1, n1, n1, n1, n1, n1, n1, n4, n8, n8, n8, n8,
		n8, n8, n4, n1, n1, n1, n1, n1, n1, n1, n4, n8, n8, n8, n8, n8, n8,
		n4, n1, n1, n1, n1, n1, n1, n1, n10, n9, n9, n9, n9, n9, n9, n11, n1,
		n13, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n13,
		n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n13, n2,
		n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n13, n2, n2,
		n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n13, n2, n2, n2,
		n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n13, n3, n3, n3, n3,
		n3, n3, n3, n3, n1, n1, n1, n1, n1, n1, n8, n8, n8, n8, n8, n8, n8,
		n8, n4, n1, n1, n1, n1, n1, n1, n8, n8, n8, n8, n8, n8, n8, n8, n4,
		n1, n1, n1, n1, n1, n1, n1, n3, n9, n9, n9, n9, n9, n9, n3, n1, n1,
		n1, n1, n1, n1, n1, n4, n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1,
		n1, n1, n1, n4, n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1,
		n1, n10, n9, n9, n9, n9, n9, n9, n11, n1, n13, n2, n2, n2, n2, n2,
		n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n13, n2, n2, n2, n2, n2, n2,
		n2, n2, n2, n2, n2, n2, n1, n1, n1, n13, n2, n2, n2, n2, n2, n2, n2,
		n2, n2, n2, n2, n1, n1, n1, n1, n13, n2, n2, n2, n2, n2, n2, n2, n2,
		n2, n2, n1, n1, n1, n1, n1, n13, n2, n2, n2, n2, n2, n2, n2, n2, n2,
		n1, n1, n1, n1, n1, n1, n13, n3, n3, n3, n3, n3, n3, n3, n3, n1, n1,
		n1, n1, n1, n1, n8, n8, n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1,
		n1, n1, n8, n8, n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1,
		n1, n3, n9, n9, n9, n9, n9, n9, n3, n1, n1, n1, n1, n1, n1, n1, n4,
		n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1, n1, n4, n8, n8,
		n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1, n1, n10, n9, n9, n9, n9,
		n9, n9, n11, n12, n12, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2,
		n2, n2, n1, n12, n12, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2,
		n1, n1, n12, n12, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1,
		n1, n12, n12, n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1,
		n12, n12, n12, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1,
		n12, n12, n3, n3, n3, n3, n3, n3, n3, n3, n1, n1, n1, n1, n1, n1,
		n12, n3, n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1, n8, n3,
		n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1, n1, n3, n9, n9,
		n9, n9, n9, n9, n3, n1, n1, n1, n1, n1, n1, n1, n4, n8, n8, n8, n8,
		n8, n8, n4, n1, n1, n1, n1, n1, n1, n1, n4, n8, n8, n8, n8, n8, n8,
		n4, n1, n1, n1, n1, n1, n1, n1, n10, n9, n9, n9, n9, n9, n9, n11, n1,
		n1, n1, n1, n1, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1,
		n1, n1, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1,
		n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n1,
		n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n1, n2, n2,
		n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n1, n3, n3, n3, n3,
		n3, n3, n3, n3, n1, n1, n1, n1, n1, n1, n8, n4, n8, n8, n14, n11, n8,
		n8, n4, n1, n1, n1, n1, n1, n1, n8, n4, n8, n14, n11, n8, n8, n8, n4,
		n1, n1, n1, n1, n1, n1, n1, n3, n14, n11, n8, n8, n8, n9, n3, n1, n1,
		n1, n1, n1, n1, n1, n4, n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1,
		n1, n1, n1, n4, n8, n8, n8, n8, n8, n8, n4, n1, n1, n1, n1, n1, n1,
		n1, n10, n9, n9, n9, n9, n9, n9, n11, n1, n1, n1, n1, n1, n1, n1, n2,
		n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n1, n2, n2, n2,
		n2, n2, n2, n2, n2, n1, n1, n1, n1, n1, n1, n1, n2, n2, n2, n2, n2,
		n2, n2, n2, n1, n1, n1, n1, n1, n1, n1, n2, n2, n2, n2, n2, n2, n2,
		n2, n1, n1, n1, n1, n1, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n1,
		n1, n1, n1, n1, n1, n1, n3, n3, n3, n3, n3, n3, n3, n3, n1, n1, n1,
		n1, n1, n1, n1, n3, n4, n4, n4, n4, n4, n4, n3, n1, n1, n1, n1, n1,
		n1, n1, n3, n4, n4, n4, n4, n4, n4, n3, n1, n1, n1, n1, n1, n1, n1,
		n3, n3, n3, n3, n3, n3, n3, n3, n1, n1, n1, n1, n1, n1, n1, n3, n4,
		n4, n4, n4, n4, n4, n3, n1, n1, n1, n1, n1, n1, n1, n3, n4, n4, n4,
		n4, n4, n4, n3, n1, n1, n1, n1, n1, n1, n1, n15, n16, n16, n16, n16,
		n16, n16, n17,
	}
}


code:
local n1 = { name = "df_underworld_items:slade_block", force_place=true }
local n2 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 1, force_place=true }
local n3 = { name = df_dependencies.node_name_stair_slade_brick, force_place=true }
local n4 = { name = df_dependencies.node_name_stair_outer_slade_brick, force_place=true }
local n5 = { name = "air", force_place=true }
local n6 = { name = "df_underworld_items:slade_seal", force_place=true }
local n7 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 1, force_place=true }
local n8 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 3, force_place=true }
local n9 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 2, force_place=true }
local n10 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 2, force_place=true }
local n11 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 3, force_place=true }

return {
	name="df_caverns:oubliette",
	size = {x = 3, y = 9, z = 3},
	center_pos = {x = 1, y = 7, z = 1},
	data = {
		n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1,
		n1, n1, n1, n1, n1, n1, n1, n2, n3, n4, n1, n1, n1, n1, n5, n1, n1,
		n5, n1, n1, n5, n1, n1, n5, n1, n1, n5, n1, n1, n5, n1, n1, n6, n1,
		n7, n5, n8, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n1,
		n1, n1, n1, n1, n1, n1, n1, n1, n1, n1, n9, n10, n11,
	},
}

repo_name: korkut/MeseCraft

license: mit


code:
local n1 = { name = "ignore" }
local n10 = { name = "air", force_place=true }

local n3 = { name = "df_underworld_items:slade_brick", force_place=true }
local n4 = { name = "df_underworld_items:slade_wall", force_place=true }
local n5 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 1, force_place=true }
local n6 = { name = df_dependencies.node_name_stair_slade_brick, force_place=true }
local n7 = { name = df_dependencies.node_name_stair_outer_slade_brick, force_place=true }
local n11 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 1, force_place=true }
local n12 = { name = df_dependencies.node_name_slab_slade_brick, param2 = 22, force_place=true}
local n13 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 3, force_place=true }
local n15 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 2, force_place=true }
local n16 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 2, force_place=true }
local n17 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 3, force_place=true }

-- foundation nodes
local n2 = { name = "df_underworld_items:slade_brick", force_place=false, place_on_condition=mapgen_helper.buildable_to }
local n8 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 2, force_place=false, place_on_condition=mapgen_helper.buildable_to }
local n9 = { name = df_dependencies.node_name_stair_slade_brick, force_place=false, place_on_condition=mapgen_helper.buildable_to }
local n14 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 1, force_place=false, place_on_condition=mapgen_helper.buildable_to }

local n18 = { name = "df_underworld_items:puzzle_chest_closed", param2 = 1, prob=192 } -- 50% chance of being force-placed.

return {
	name = "df_caverns:small_building",
	size = {x = 11, y = 9, z = 7},
	center_pos = {x = 7, y = 5, z = 3},
	data = {
		n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n2, n2, n2, n2,
		n2, n2, n2, n2, n2, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1,
		n1, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n2, n2, n2,
		n2, n2, n2, n2, n1, n1, n1, n1, n3, n3, n3, n3, n3, n3, n3, n1, n1,
		n1, n1, n3, n4, n4, n4, n4, n4, n3, n1, n1, n1, n1, n3, n4, n4, n4,
		n4, n4, n3, n1, n1, n1, n1, n5, n6, n6, n6, n6, n6, n7, n1, n1, n2,
		n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n2, n2, n2, n2, n2, n2, n2,
		n2, n2, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n8, n2,
		n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n9, n2, n2, n2, n2, n2, n2,
		n2, n1, n1, n1, n1, n3, n3, n3, n3, n3, n3, n3, n1, n1, n1, n10, n4,
		n10, n10, n10, n10, n10, n4, n1, n1, n1, n10, n4, n10, n10, n10, n10,
		n10, n4, n1, n1, n1, n1, n11, n12, n12, n12, n12, n12, n13, n1, n1,
		n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n2, n2, n2, n2, n2, n2,
		n2, n2, n2, n1, n1, n8, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1,
		n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n2, n2, n2, n2, n2, n2,
		n2, n2, n1, n1, n1, n9, n3, n3, n3, n3, n3, n3, n3, n1, n1, n1, n10,
		n3, n10, n10, n10, n10, n10, n4, n1, n1, n1, n10, n3, n10, n10, n10,
		n10, n10, n4, n1, n1, n1, n1, n11, n12, n12, n12, n12, n12, n13, n14,
		n2, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n14, n2, n2, n2, n2, n2,
		n2, n2, n2, n2, n1, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1,
		n1, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n2, n2, n2, n2, n2,
		n2, n2, n2, n1, n1, n1, n2, n3, n3, n3, n3, n3, n3, n3, n1, n1, n1,
		n10, n10, n10, n10, n10, n10, n18, n4, n1, n1, n1, n10, n10, n10,
		n10, n10, n10, n10, n4, n1, n1, n1, n1, n11, n12, n12, n12, n12, n12,
		n13, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n2, n2, n2,
		n2, n2, n2, n2, n2, n2, n1, n1, n9, n2, n2, n2, n2, n2, n2, n2, n2,
		n1, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n2, n2, n2,
		n2, n2, n2, n2, n2, n1, n1, n1, n8, n3, n3, n3, n3, n3, n3, n3, n1,
		n1, n1, n10, n3, n10, n10, n10, n10, n10, n4, n1, n1, n1, n10, n3,
		n10, n10, n10, n10, n10, n4, n1, n1, n1, n1, n11, n12, n12, n12, n12,
		n12, n13, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n2, n2,
		n2, n2, n2, n2, n2, n2, n2, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2,
		n2, n1, n1, n9, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n8, n2,
		n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n3, n3, n3, n3, n3, n3, n3,
		n1, n1, n1, n10, n4, n10, n10, n10, n10, n10, n4, n1, n1, n1, n10,
		n4, n10, n10, n10, n10, n10, n4, n1, n1, n1, n1, n11, n12, n12, n12,
		n12, n12, n13, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1,
		n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n2, n2, n2, n2, n2, n2,
		n2, n2, n2, n1, n1, n2, n2, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1,
		n1, n2, n2, n2, n2, n2, n2, n2, n1, n1, n1, n1, n3, n3, n3, n3, n3,
		n3, n3, n1, n1, n1, n1, n3, n4, n4, n4, n4, n4, n3, n1, n1, n1, n1,
		n3, n4, n4, n4, n4, n4, n3, n1, n1, n1, n1, n15, n16, n16, n16, n16,
		n16, n17,
	}
}


code:
local n1 = { name = "df_underworld_items:slade_brick", force_place=false, place_on_condition=mapgen_helper.buildable_to }
local n2 = { name = df_dependencies.node_name_stair_inner_slade_brick, param2 = 1, force_place=true }
local n3 = { name = df_dependencies.node_name_stair_inner_slade_brick, force_place=true }
local n4 = { name = "df_underworld_items:slade_brick", force_place=true }
local n5 = { name = df_dependencies.node_name_stair_inner_slade_brick, param2 = 2, force_place=true }
local n6 = { name = df_dependencies.node_name_stair_inner_slade_brick, param2 = 3, force_place=true }

return {
	name = "df_caverns:small_slab",
	size = {y = 2, x = 2, z = 3},
	center_pos = {x = 1, y = 1, z = 1},
	data = {
		n1, n1, n2, n3, n1, n1, n4, n4, n1, n1, n5, n6,
	}
}


code:
-- This file contains code that is used by multiple different cavern layers.

local c_air = df_caverns.node_id.air
local c_cobble = df_caverns.node_id.cobble
local c_cobble_fungus = df_caverns.node_id.cobble_fungus
local c_cobble_fungus_fine = df_caverns.node_id.cobble_fungus_fine
local c_dead_fungus = df_caverns.node_id.dead_fungus
local c_dirt = df_caverns.node_id.dirt
local c_dirt_moss = df_caverns.node_id.dirt_moss
local c_dry_flowstone = df_caverns.node_id.dry_flowstone
local c_fireflies = df_caverns.node_id.fireflies
local c_glowstone = df_caverns.node_id.glowstone
local c_ice = df_caverns.node_id.ice
local c_mossycobble = df_caverns.node_id.mossycobble
local c_oil = df_caverns.node_id.oil
local c_sand_scum = df_caverns.node_id.sand_scum
local c_spongestone = df_caverns.node_id.spongestone
local c_rock_rot = df_caverns.node_id.rock_rot
local c_water = df_caverns.node_id.water
local c_wet_flowstone = df_caverns.node_id.wet_flowstone
local c_webs = df_caverns.node_id.big_webs
local c_webs_egg = df_caverns.node_id.big_webs_egg

df_caverns.data_param2 = {} -- shared among all mapgens to reduce memory clutter

local get_biome_at_pos_list = {} -- a list of methods of the form function(pos, heat, humidity) to allow modpack-wide queries about what should grow where
df_caverns.register_biome_check = function(func)
	table.insert(get_biome_at_pos_list, func)
end
df_caverns.get_biome = function(pos)
	local heat = minetest.get_heat(pos)
	local humidity = minetest.get_humidity(pos)
	for _, val in pairs(get_biome_at_pos_list) do
		local biome = val(pos, heat, humidity)
		if biome ~= nil then
			return biome
		end
	end
end

-- for testing
--local debug_timer = 0
--minetest.register_globalstep(function(dtime)
--	debug_timer = debug_timer + dtime
--	if debug_timer > 5 then
--		local singleplayer = minetest.get_player_by_name("singleplayer")
--		if singleplayer then
--			minetest.debug(df_caverns.get_biome(singleplayer:get_pos()))
--		end
--		debug_timer = debug_timer - 5
--	end
--end)

-- prevent mapgen from using these nodes as a base for stalactites or stalagmites
local dont_build_speleothems_on = {}
for _, content_id in pairs(df_mapitems.wet_stalagmite_ids) do
	dont_build_speleothems_on[content_id] = true
end
for _, content_id in pairs(df_mapitems.dry_stalagmite_ids) do
	dont_build_speleothems_on[content_id] = true
end
if minetest.get_modpath("big_webs") then
	dont_build_speleothems_on[c_webs] = true
	dont_build_speleothems_on[c_webs_egg] = true
end

--------------------------------------------------

df_caverns.stalagmites = function(abs_cracks, vert_rand, vi, area, data, data_param2, wet, reverse_sign)
	if dont_build_speleothems_on[data[vi]] then
		return
	end
	local flowstone
	local stalagmite_ids
	if wet then
		flowstone = c_wet_flowstone
		stalagmite_ids = df_mapitems.wet_stalagmite_ids
	else
		flowstone = c_dry_flowstone
		stalagmite_ids = df_mapitems.dry_stalagmite_ids
	end

	local height_mult = 1
	local ystride = area.ystride
	if reverse_sign then
		ystride = - ystride
		height_mult = -1
	end

	if vert_rand < 0.004 then
		if reverse_sign then
			subterrane.big_stalactite(vi+ystride, area, data, 6, 15, flowstone, flowstone, flowstone)
		else
			subterrane.big_stalagmite(vi+ystride, area, data, 6, 15, flowstone, flowstone, flowstone)
		end
	else
		local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
		local height = math.floor(abs_cracks * 50)
		subterrane.stalagmite(vi+ystride, area, data, data_param2, param2, height*height_mult, stalagmite_ids)
	end
	data[vi] = flowstone
end

df_caverns.stalactites = function(abs_cracks, vert_rand, vi, area, data, data_param2, wet)
	df_caverns.stalagmites(abs_cracks, vert_rand, vi, area, data, data_param2, wet, true)
end

--------------------------------------------------

df_caverns.flooded_cavern_floor = function(abs_cracks, vert_rand, vi, area, data)
	local ystride = area.ystride
	if abs_cracks < 0.25 then
		data[vi] = c_mossycobble
	elseif data[vi-ystride] ~= c_water then
		data[vi] = c_sand_scum
	end

	-- put in only the large stalagmites that won't get in the way of the water
	if abs_cracks < 0.1 then
		if vert_rand < 0.004 then
			subterrane.big_stalagmite(vi+ystride, area, data, 6, 15, c_wet_flowstone, c_wet_flowstone, c_wet_flowstone)
		end
	end
end

df_caverns.dry_cavern_floor = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	if abs_cracks < 0.075 then
		df_caverns.stalagmites(abs_cracks, vert_rand, vi, area, data, data_param2, false)
	elseif abs_cracks < 0.4 then
		data[vi] = c_cobble
	elseif abs_cracks < 0.6 then
		data[vi] = c_cobble_fungus_fine
	else
		data[vi] = c_cobble_fungus
		if c_dead_fungus and math.random() < 0.05 then
			data[vi+area.ystride] = c_dead_fungus
		end
	end
end

df_caverns.wet_cavern_floor = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	if abs_cracks < 0.1 then
		df_caverns.stalagmites(abs_cracks, vert_rand, vi, area, data, data_param2, true)
	elseif abs_cracks < 0.6 then
		data[vi] = c_cobble
	elseif abs_cracks < 0.8 then
		data[vi] = c_rock_rot
	else
		data[vi] = c_spongestone
		if c_dead_fungus and math.random() < 0.05 then
			data[vi+area.ystride] = c_dead_fungus
		end
	end
end

--------------------------------------

df_caverns.glow_worm_cavern_ceiling = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	if abs_cracks < 0.1 then
		df_caverns.stalactites(abs_cracks, vert_rand, vi, area, data, data_param2, true)
	elseif abs_cracks < 0.5 and abs_cracks > 0.3 and math.random() < 0.3 then
		df_mapitems.glow_worm_ceiling(area, data, vi-area.ystride)
	end
end

df_caverns.tunnel_floor = function(minp, maxp, area, vi, nvals_cracks, data, data_param2, wet, dirt_node)
	if maxp.y > -30 then
		wet = false
	end
	local ystride = area.ystride
	local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
	local cracks = nvals_cracks[index2d]
	local abs_cracks = math.abs(cracks)

	if wet then
		if abs_cracks < 0.05 and data[vi+ystride] == c_air and not dont_build_speleothems_on[data[vi]] then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
			local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
			local height = math.floor(abs_cracks * 100)
			subterrane.stalagmite(vi+ystride, area, data, data_param2, param2, height, df_mapitems.wet_stalagmite_ids)
			data[vi] = c_wet_flowstone
		elseif dirt_node and abs_cracks > 0.5 and data[vi-ystride] ~= c_air then
			data[vi] = dirt_node
		end
	else
		if abs_cracks < 0.025 and data[vi+ystride] == c_air and not dont_build_speleothems_on[data[vi]] then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
			local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
			local height = math.floor(abs_cracks * 100)
			subterrane.stalagmite(vi+ystride, area, data, data_param2, param2, height, df_mapitems.dry_stalagmite_ids)
		elseif dirt_node and cracks > 0.5 and data[vi-ystride] ~= c_air then
			data[vi] = dirt_node
		end
	end
end

df_caverns.tunnel_ceiling = function(minp, maxp, area, vi, nvals_cracks, data, data_param2, wet)
	if maxp.y > -30 then
		wet = false
	end

	local ystride = area.ystride
	local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
	local cracks = nvals_cracks[index2d]
	local abs_cracks = math.abs(cracks)

	if wet then
		if abs_cracks < 0.05 and data[vi-ystride] == c_air and not dont_build_speleothems_on[data[vi]] then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
			local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
			local height = math.floor(abs_cracks * 100)
			subterrane.stalactite(vi-ystride, area, data, data_param2, param2, height, df_mapitems.wet_stalagmite_ids)
			data[vi] = c_wet_flowstone
		end
	else
		if abs_cracks < 0.025 and data[vi-ystride] == c_air and not dont_build_speleothems_on[data[vi]] then -- make sure data[vi] is not already flowstone. Stalagmites from lower levels are acting as base for further stalagmites
			local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
			local height = math.floor(abs_cracks * 100)
			subterrane.stalactite(vi-ystride, area, data, data_param2, param2, height, df_mapitems.dry_stalagmite_ids)
		end
	end
end

df_caverns.perlin_cave = {
	offset = 0,
	scale = 1,
	spread = {x=df_caverns.config.horizontal_cavern_scale, y=df_caverns.config.vertical_cavern_scale, z=df_caverns.config.horizontal_cavern_scale},
	seed = -400000000089,
	octaves = 3,
	persist = 0.67
}

df_caverns.perlin_wave = {
	offset = 0,
	scale = 1,
	spread = {x=df_caverns.config.horizontal_cavern_scale * 2, y=df_caverns.config.vertical_cavern_scale, z=df_caverns.config.horizontal_cavern_scale * 2}, -- squashed 2:1
	seed = 59033,
	octaves = 6,
	persist = 0.63
}

-- Used for making lines of dripstone, and in various other places where small-scale patterns are needed
df_caverns.np_cracks = {
	offset = 0,
	scale = 1,
	spread = {x = 20, y = 20, z = 20},
	seed = 5717,
	octaves = 3,
	persist = 0.63,
	lacunarity = 2.0,
}

---------------------------------------------------------------------------------

df_caverns.place_shrub = function(vi, area, data, param2_data, shrub_list)
	if shrub_list == nil then
		return
	end

	local shrub = shrub_list[math.random(#shrub_list)]
	shrub(vi, area, data, param2_data)
end

---------------------------------------------------------------------------------
-- This method allows subterrane to overgenerate caves without destroying any of the decorations
local dfcaverns_nodes = nil
local dfcaverns_mods = {
	"df_farming:",
	"df_mapitems:",
	"df_primordial_items:",
	"df_trees:",
	"df_underworld_items:",
	"ice_sprites:",
	"mine_gas:",
}

df_caverns.is_ground_content = function(c_node)
	if dfcaverns_nodes then
		return not dfcaverns_nodes[c_node]
	end
	dfcaverns_nodes = {}
	for k, v in pairs(minetest.registered_nodes) do
		for _, prefix in ipairs(dfcaverns_mods) do
			if k:sub(1, #prefix) == prefix then
				dfcaverns_nodes[minetest.get_content_id(k)] = true
			end
		end
	end
	dfcaverns_nodes[c_ice] = true -- needed for nethercap cavern water covering
	dfcaverns_nodes[c_oil] = true -- needed for blackcap oil slicks
	if c_fireflies then
		dfcaverns_nodes[c_fireflies] = true -- used in the primordial caverns
	end
	dfcaverns_nodes[c_glowstone] = nil
	dfcaverns_mods = nil
	return not dfcaverns_nodes[c_node]
end


code:
local c_water = df_caverns.node_id.water
local c_air = df_caverns.node_id.air
local c_dirt = df_caverns.node_id.dirt
local c_dirt_moss = df_caverns.node_id.dirt_moss
local c_sand = df_caverns.node_id.sand
local c_gravel = df_caverns.node_id.gravel
local c_wet_flowstone = df_caverns.node_id.wet_flowstone
local c_dry_flowstone = df_caverns.node_id.dry_flowstone
local c_lava = df_caverns.node_id.lava
local c_obsidian = df_caverns.node_id.obsidian

local chasms_path = minetest.get_modpath("chasms")

local log_location
if mapgen_helper.log_location_enabled then
	log_location = mapgen_helper.log_first_location
end

local c_coral_table = {}
for node_name, node_def in pairs(minetest.registered_nodes) do
	if minetest.get_item_group(node_name, "dfcaverns_cave_coral") > 0 then
		table.insert(c_coral_table, minetest.get_content_id(node_name))
	end
end

local towergoblin_shrublist
local fungispore_shrublist

if minetest.get_modpath("df_farming") then
	towergoblin_shrublist = {
		df_farming.spawn_plump_helmet_vm,
		df_farming.spawn_plump_helmet_vm,
		df_farming.spawn_dimple_cup_vm,
		df_farming.spawn_dead_fungus_vm,
		df_farming.spawn_cavern_fungi_vm,
	}
	fungispore_shrublist = 	{
		df_farming.spawn_pig_tail_vm,
		df_farming.spawn_sweet_pod_vm,
		df_farming.spawn_cave_wheat_vm,
		df_farming.spawn_cave_wheat_vm,
		df_farming.spawn_dead_fungus_vm,
		df_farming.spawn_cavern_fungi_vm,
	}
end

------------------------------------------------------------------------------------------

local perlin_cave_sunless_sea = {
	offset = 0,
	scale = 1,
	spread = {x=df_caverns.config.horizontal_cavern_scale * 2, y=df_caverns.config.vertical_cavern_scale * 0.5, z=df_caverns.config.horizontal_cavern_scale * 2},
	seed = -400000000089,
	octaves = 3,
	persist = 0.67
}

local perlin_wave_sunless_sea = {
	offset = 0,
	scale = 1,
	spread = {x=df_caverns.config.horizontal_cavern_scale * 4, y=df_caverns.config.vertical_cavern_scale * 0.5, z=df_caverns.config.horizontal_cavern_scale * 4}, -- squashed 2:1
	seed = 59033,
	octaves = 6,
	persist = 0.63
}

local perlin_cave_rivers = {
	offset = 0,
	scale = 1,
	spread = {x=400, y=400, z=400},
	seed = -400000000089,
	octaves = 3,
	persist = 0.67,
	flags = "", -- remove "eased" flag, makes the paths of rivers a bit jaggedier and more interesting that curvy smooth paths
}

-- large-scale rise and fall to make the seam between roof and floor less razor-flat and make the rivers shallower and deeper in various places
local perlin_wave_rivers = {
	offset = 0,
	scale = 1,
	spread = {x=800, y=800, z=800},
	seed = -4000089,
	octaves = 3,
	persist = 0.67,
}

local sea_level = df_caverns.config.level3_min - (df_caverns.config.level3_min - df_caverns.config.sunless_sea_min) * 0.5

local floor_mult = 100
local floor_displace = -10
local ceiling_mult = -200
local ceiling_displace = 20
local wave_mult = 7
local ripple_mult = 15
local y_max_river = sea_level + 2*wave_mult + ceiling_displace + ripple_mult
local y_min_river = sea_level - 2*wave_mult + floor_displace

local hot_zone_boundary = 70
local middle_zone_boundary = 50
local cool_zone_boundary = 30

df_caverns.register_biome_check(function(pos, heat, humidity)
	if pos.y < df_caverns.config.sunless_sea_min or pos.y >= df_caverns.config.level3_min then
		return nil
	end
	if pos.y < sea_level then
		return "sunless undersea"
	elseif heat > hot_zone_boundary then
		return "barren" -- hot zone
	elseif heat > middle_zone_boundary then
		return "fungispore"
	elseif heat > cool_zone_boundary then
		return "towergoblin"
	else
		return "barren" -- cool zone
	end
end)

local towergoblin_cavern_floor = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	local ystride = area.ystride
	if abs_cracks < 0.1 then
		df_caverns.stalagmites(abs_cracks, vert_rand, vi, area, data, data_param2, true)
	elseif data[vi-ystride] ~= c_air and data[vi-ystride] ~= c_water then -- leave the ground as rock if it's only one node thick
		if math.random() < 0.25 then
			data[vi] = c_dirt
		else
			data[vi] = c_dirt_moss
		end
		if math.random() < 0.1 then
			df_caverns.place_shrub(vi+ystride, area, data, data_param2, towergoblin_shrublist)
		elseif abs_cracks > 0.25 then
			if math.random() < 0.01 then
				df_trees.spawn_tower_cap_vm(vi+ystride, area, data)
			elseif math.random() < 0.01 then
				df_trees.spawn_goblin_cap_vm(vi+ystride, area, data, data_param2)
			elseif math.random() < 0.02 then
				df_trees.spawn_spindlestem_vm(vi+ystride, area, data, data_param2)
			end
		end
	end
end

local fungispore_cavern_floor = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	local ystride = area.ystride
	if abs_cracks < 0.1 then
		df_caverns.stalagmites(abs_cracks, vert_rand, vi, area, data, data_param2, true)
	elseif data[vi-ystride] ~= c_air and data[vi-ystride] ~= c_water then -- leave the ground as rock if it's only one node thick
		if math.random() < 0.25 then
			data[vi] = c_dirt
		else
			data[vi] = c_dirt_moss
		end
		if math.random() < 0.1 then
			df_caverns.place_shrub(vi+ystride, area, data, data_param2, fungispore_shrublist)
		elseif abs_cracks > 0.35 then
			if math.random() < 0.025 then
				df_trees.spawn_fungiwood_vm(vi+ystride, area, data)
			elseif math.random() < 0.025 then
				df_trees.spawn_spore_tree_vm(vi+ystride, area, data, data_param2)
			end
		end
	end
end

local cool_zone_ceiling = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	if abs_cracks < 0.1 then
		df_caverns.stalactites(abs_cracks, vert_rand, vi, area, data, data_param2, false)
	end
end

local hot_zone_ceiling = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	-- dry zone ceiling, add crystals
	if abs_cracks < 0.1 then
		df_caverns.stalactites(abs_cracks, vert_rand, vi, area, data, data_param2, false)
	end
	if abs_cracks > 0.3 and math.random() < 0.005 then
		df_mapitems.place_big_crystal_cluster(area, data, data_param2, vi, math.random(0,3), true)
	end
end

local cool_zone_floor = df_caverns.dry_cavern_floor

local hot_zone_floor = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	if abs_cracks < 0.075 then
		df_caverns.stalagmites(abs_cracks, vert_rand, vi, area, data, data_param2, false)
	elseif abs_cracks > 0.3 and math.random() < 0.005 then
		df_mapitems.place_big_crystal_cluster(area, data, data_param2, vi+area.ystride,  math.random(0,2), false)
	end
end

local decorate_sunless_sea = function(minp, maxp, seed, vm, node_arrays, area, data)
	math.randomseed(minp.x + minp.y*2^8 + minp.z*2^16 + seed) -- make decorations consistent between runs

	local heatmap = minetest.get_mapgen_object("heatmap")
	local data_param2 = df_caverns.data_param2
	vm:get_param2_data(data_param2)
	local nvals_cracks = mapgen_helper.perlin2d("df_cavern:cracks", minp, maxp, df_caverns.np_cracks)

	local minp_below = minp.y <= sea_level
	local maxp_above = maxp.y > sea_level

	local nvals_cave = mapgen_helper.perlin2d("df_caverns:sunless_sea", minp, maxp, perlin_cave_rivers) --cave noise for structure
	local nvals_wave = mapgen_helper.perlin2d("df_caverns:sunless_sea_wave", minp, maxp, perlin_wave_rivers) --cave noise for structure

	local skip_next = false -- mapgen is proceeding upward on the y axis,
		--if this is true it skips a step to allow for things to be placed above the floor

	-- creates "river" caverns
	for vi, x, y, z in area:iterp_yxz(minp, maxp) do
		if not skip_next then
			if y < y_max_river and y > y_min_river then
				local index2d = mapgen_helper.index2d(minp, maxp, x, z)
				local abs_cave = math.abs(nvals_cave[index2d])
				local wave = nvals_wave[index2d] * wave_mult
				local cracks = nvals_cracks[index2d]

				local ripple = cracks * ((y - y_min_river) / (y_max_river - y_min_river)) * ripple_mult

				-- above floor and below ceiling
				local floor_height = math.floor(abs_cave * floor_mult + sea_level + floor_displace + wave)
				local ceiling_height = math.floor(abs_cave * ceiling_mult + sea_level + ceiling_displace + wave + ripple)

				-- deal with lava
				if y <= floor_height and y > floor_height - 3 and y < sea_level + 5 and data[vi] == c_lava then
					data[vi] = c_obsidian
				end

				if y == floor_height and y < sea_level and not mapgen_helper.buildable_to(data[vi]) then
					if cracks > 0.2 then
						data[vi] = c_sand
						if cracks > 0.5 then
							data[vi+area.ystride] = c_sand
							skip_next = true
						end
					else
						data[vi] = c_gravel
					end
				elseif y > floor_height and y < ceiling_height and data[vi] ~= c_wet_flowstone then
					data[vi] = c_air
					if log_location then log_location("sunless_sea_river", vector.new(x,y,z)) end
				elseif y == ceiling_height and not mapgen_helper.buildable_to(data[vi]) then
					df_caverns.glow_worm_cavern_ceiling(math.abs(cracks),
						mapgen_helper.xz_consistent_randomi(area, vi), vi, area, data, data_param2)
				end

				-- Deal with lava
				if y >= ceiling_height and y < ceiling_height + 5 and y > sea_level - 5 and data[vi] == c_lava then
					data[vi] = c_obsidian
				end
			end
		else
			skip_next = false
		end
	end

	if minp.y <= sea_level then
		for vi, x, y, z in area:iterp_yxz(area.MinEdge, area.MaxEdge) do
			-- convert all air below sea level into water
			if y <= sea_level and data[vi] == c_air then
				data[vi] = c_water
			end
		end
	end

	---------------------------------------------------------
	-- Cavern floors

	for _, vi in ipairs(node_arrays.cavern_floor_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local heat = heatmap[index2d]
		local cracks = nvals_cracks[index2d]
		local abs_cracks = math.abs(cracks)
		local vert_rand = mapgen_helper.xz_consistent_randomi(area, vi)
		local y = area:get_y(vi)

		-- The vertically squished aspect of these caverns produces too many very thin shelves, this blunts them
		if mapgen_helper.buildable_to(data[vi-area.ystride]) then
			if y <= sea_level then
				data[vi] = c_water
			else
				data[vi] = c_air
			end
		end

		-- extra test is needed because the rivers can remove nodes that Subterrane marked as floor.
		if not mapgen_helper.buildable_to(data[vi]) then
			if y >= sea_level  then
				if heat > hot_zone_boundary then
					hot_zone_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
					if log_location then log_location("sunless_sea_hot", area:position(vi)) end
				elseif heat > middle_zone_boundary then
					fungispore_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
					if log_location then log_location("sunless_sea_fungispore", area:position(vi)) end
				elseif heat > cool_zone_boundary then
					towergoblin_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
					if log_location then log_location("sunless_sea_towergoblin", area:position(vi)) end
				else
					cool_zone_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
					if log_location then log_location("sunless_sea_cool", area:position(vi)) end
				end
			elseif y >= sea_level - 30 then
				if math.random() < 0.005 then
					df_mapitems.place_snareweed_patch(area, data, vi, data_param2, 6)
				else
					data[vi] = c_dirt
				end
			else
				data[vi] = c_sand
				if math.random() < 0.001 then
					local iterations = math.random(1, 6)
					df_mapitems.spawn_coral_pile(area, data, vi, iterations)
					df_mapitems.spawn_castle_coral(area, data, vi+area.ystride, iterations)
					if log_location then log_location("sunless_sea_castle_coral", area:position(vi)) end
				end
			end
		end
	end

	--------------------------------------
	-- Cavern ceilings

	for _, vi in ipairs(node_arrays.cavern_ceiling_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local heat = heatmap[index2d]
		local cracks = nvals_cracks[index2d]
		local abs_cracks = math.abs(cracks)
		local vert_rand = mapgen_helper.xz_consistent_randomi(area, vi)
		local y = area:get_y(vi)

		if y > sea_level and not mapgen_helper.buildable_to(data[vi]) then
			if heat > hot_zone_boundary then
				hot_zone_ceiling(abs_cracks, vert_rand, vi, area, data, data_param2)
			elseif heat > cool_zone_boundary then
				df_caverns.glow_worm_cavern_ceiling(abs_cracks, vert_rand, vi, area, data, data_param2)
			else
				cool_zone_ceiling(abs_cracks, vert_rand, vi, area, data, data_param2)
			end
		end

	end

		----------------------------------------------
	-- Tunnel floors

	for _, vi in ipairs(node_arrays.tunnel_floor_nodes) do
		if area:get_y(vi) >= sea_level and not mapgen_helper.buildable_to(data[vi]) then
			df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
		end
	end

	------------------------------------------------------
	-- Tunnel ceiling

	for _, vi in ipairs(node_arrays.tunnel_ceiling_nodes) do
		if area:get_y(vi) > sea_level and not mapgen_helper.buildable_to(data[vi]) then
			df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
		else
			-- air pockets
			local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
			local cracks = nvals_cracks[index2d]
			local ystride = area.ystride
			if cracks > 0.6 and data[vi-ystride] == c_water then
				data[vi-ystride] = c_air
				if cracks > 0.8 and data[vi-ystride*2] == c_water then
					data[vi-ystride*2] = c_air
				end
			end
		end
	end

	------------------------------------------------------
	-- Warren ceiling

	for _, vi in ipairs(node_arrays.warren_ceiling_nodes) do
		if area:get_y(vi) > sea_level and not mapgen_helper.buildable_to(data[vi]) then
			df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
		else
			-- air pockets
			local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
			local cracks = nvals_cracks[index2d]
			local ystride = area.ystride
			if cracks > 0.6 and data[vi-ystride] == c_water then
				data[vi-ystride] = c_air
				if cracks > 0.8 and data[vi-ystride*2] == c_water then
					data[vi-ystride*2] = c_air
				end
			end
		end
	end

	----------------------------------------------
	-- Warren floors

	for _, vi in ipairs(node_arrays.warren_floor_nodes) do
		if area:get_y(vi) >= sea_level and not mapgen_helper.buildable_to(data[vi]) then
			df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			if log_location then log_location("sunless_sea_warren", area:position(vi)) end
		end
	end

	-- columns
	for _, vi in ipairs(node_arrays.column_nodes) do
		if data[vi] == c_wet_flowstone then
			if area:get_y(vi) > sea_level then
				local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
				local heat = heatmap[index2d]
				if heat > hot_zone_boundary then
					data[vi] = c_dry_flowstone
				end
			else
				data[vi] = c_coral_table[math.random(1,3)]
				data_param2[vi] = math.random(1,4)-1
				minetest.get_node_timer(area:position(vi)):start(math.random(10, 60))
			end

			if chasms_path then
				local pos = area:position(vi)
				if chasms.is_in_chasm(pos) then
					if pos.y <= sea_level then
						data[vi] = c_water
					else
						data[vi] = c_air
					end
				end
			end
		end
	end

	vm:set_param2_data(data_param2)
end

--Sunless Sea
subterrane.register_layer({
	name = "sunless sea",
	y_max = df_caverns.config.level3_min-1,
	y_min = df_caverns.config.sunless_sea_min,
	cave_threshold = df_caverns.config.sunless_sea_threshold,
	perlin_cave = perlin_cave_sunless_sea,
	perlin_wave = perlin_wave_sunless_sea,
	solidify_lava = true,
	columns = {
		maximum_radius = 20,
		minimum_radius = 5,
		node = "df_mapitems:wet_flowstone",
		weight = 0.5,
		maximum_count = 60,
		minimum_count = 10,
	},
	decorate = decorate_sunless_sea,
	double_frequency = false,
	is_ground_content = df_caverns.is_ground_content,
})


code:
-- surface tunnels

local y_max = 200
local y_min = df_caverns.config.ymax

local c_stone = df_caverns.node_id.stone
local c_air = df_caverns.node_id.air

minetest.register_on_generated(function(minp, maxp, seed)
	--if out of range of cave definition limits, abort
	if minp.y > y_max or maxp.y < y_min then
		return
	end

	local t_start = os.clock()

	local vm, data, data_param2, area = mapgen_helper.mapgen_vm_data_param2()
	local data_changed = false

	local eminp = {x=minp.x, y=area.MinEdge.y, z=minp.z}
	local emaxp = {x=maxp.x, y=area.MaxEdge.y, z=maxp.z}
	local minp_y = minp.y
	local maxp_y = maxp.y

	local humiditymap = minetest.get_mapgen_object("humiditymap")
	local nvals_cracks = mapgen_helper.perlin2d("df_cavern:cracks", minp, maxp, df_caverns.np_cracks)

	local previous_y = eminp.y-1

	local previous_potential_floor_vi
	local previous_potential_floor_y
	local previous_node

	for vi, x, y, z in area:iterp_yxz(eminp, emaxp) do

		if y < previous_y then
			-- we've started a new column, initialize everything
			previous_potential_floor_vi = nil
			previous_potential_floor_y = nil
			previous_node = nil
		end
		previous_y = y

		local current_node = data[vi]
		if previous_node and y < y_max then
			if current_node == c_air and previous_node == c_stone then
				-- this may be a floor, but only if we eventually hit a ceiling in this column
				previous_potential_floor_vi = vi-area.ystride
				previous_potential_floor_y = y-1
			elseif current_node == c_stone and previous_node == c_air and previous_potential_floor_vi then
				-- we hit a ceiling after passing through a floor
				local index2d = mapgen_helper.index2d(minp, maxp, x, z)
				local humidity = humiditymap[index2d]
				if previous_potential_floor_y <= maxp_y and previous_potential_floor_y >= minp_y then
					data_changed = true
					df_caverns.tunnel_floor(minp, maxp, area, previous_potential_floor_vi, nvals_cracks, data, data_param2, humidity > 30)
				end
				if y <= maxp_y and y >= minp_y then
					data_changed = true
					df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, humidity > 30)
				end
				previous_potential_floor_vi = nil
			elseif not mapgen_helper.buildable_to(current_node) then
				-- we've entered a non-stone ceiling of some kind. Abort potential floor-ceiling pair detection.
				previous_potential_floor_vi = nil
			end
		end
		previous_node = current_node

	end

	if data_changed then
		--send data back to voxelmanip
		vm:set_data(data)
		vm:set_param2_data(data_param2)
		--calc lighting
		vm:set_lighting({day = 0, night = 0})
		vm:calc_lighting()

		vm:update_liquids()
		--write it to world
		vm:write_to_map()
	end

	local time_taken = os.clock() - t_start -- how long this chunk took, in seconds
	mapgen_helper.record_time("df_caverns surface tunnels", time_taken)
end)


code:
if not df_caverns.config.enable_underworld or not minetest.get_modpath("df_underworld_items") then
	return
end
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local S = minetest.get_translator(modname)

local bones_loot_path = minetest.get_modpath("bones_loot")
local bones_node = df_dependencies.node_name_bones
local named_waypoints_path = minetest.get_modpath("named_waypoints")
local name_generator_path = minetest.get_modpath("name_generator")

local hunters_enabled = minetest.get_modpath("hunter_statue") and df_underworld_items.config.underworld_hunter_statues

local log_location
if mapgen_helper.log_location_enabled then
	log_location = mapgen_helper.log_first_location
end

local name_pit = function() end
local name_ruin = function() end

if named_waypoints_path then

	local item_required = nil
	if minetest.settings:get_bool("dfcaverns_underworld_hud_requires_item", true) then
		local setting_item_required = minetest.settings:get("dfcaverns_underworld_hud_item_required")
		if setting_item_required == nil or setting_item_required == "" then
			setting_item_required = "map:mapping_kit"
		end
		item_required = setting_item_required
	end

	local pit_waypoint_def = {
		default_name = S("A glowing pit"),
		default_color = 0xFF88FF,
		discovery_volume_radius = tonumber(minetest.settings:get("dfcaverns_pit_discovery_range")) or 60,
		visibility_requires_item = item_required,
	}

	if minetest.settings:get_bool("dfcaverns_show_pits_in_hud", true) then
		pit_waypoint_def.visibility_volume_radius = tonumber(minetest.settings:get("dfcaverns_pit_visibility_range")) or 500
		pit_waypoint_def.on_discovery = named_waypoints.default_discovery_popup
	end
	named_waypoints.register_named_waypoints("glowing_pits", pit_waypoint_def)

	local seal_waypoint_def = {
		default_name = S("Mysterious seal"),
		default_color = 0x9C2233,
		discovery_volume_radius = tonumber(minetest.settings:get("dfcaverns_seal_discovery_range")) or 10,
		visibility_requires_item = item_required,
	}

	if minetest.settings:get_bool("dfcaverns_show_seals_in_hud", true) then
		seal_waypoint_def.visibility_volume_radius = tonumber(minetest.settings:get("dfcaverns_seal_visibility_range")) or 200
		seal_waypoint_def.on_discovery = named_waypoints.default_discovery_popup
	end
	named_waypoints.register_named_waypoints("puzzle_seals", seal_waypoint_def)

	if name_generator_path then
		name_generator.parse_lines(io.lines(modpath.."/underworld_names.cfg"))

		name_pit = function()
			return name_generator.generate("glowing_pits")
		end
		name_ruin = function()
			return name_generator.generate("underworld_ruins")
		end

		local underworld_ruin_def = {
			default_name = S("Ancient ruin"),
			discovery_volume_radius = tonumber(minetest.settings:get("dfcaverns_ruin_discovery_range")) or 40,
			visibility_requires_item = item_required,
		}
		if minetest.settings:get_bool("dfcaverns_show_ruins_in_hud", true) then
			underworld_ruin_def.visibility_volume_radius = tonumber(minetest.settings:get("dfcaverns_ruin_visibility_range")) or 250
			underworld_ruin_def.on_discovery = named_waypoints.default_discovery_popup
		end

		named_waypoints.register_named_waypoints("underworld_ruins", underworld_ruin_def)
	end
end

local c_slade = df_caverns.node_id.slade
local c_slade_block = df_caverns.node_id.slade_block
local c_air = df_caverns.node_id.air
local c_water = df_caverns.node_id.water

local c_glowstone = df_caverns.node_id.glowstone
local c_amethyst = df_caverns.node_id.amethyst
local c_pit_plasma = df_caverns.node_id.pit_plasma

local MP = minetest.get_modpath(minetest.get_current_modname())
local oubliette_schematic = dofile(MP.."/schematics/oubliette.lua")
local lamppost_schematic = dofile(MP.."/schematics/lamppost.lua")
local small_slab_schematic = dofile(MP.."/schematics/small_slab.lua")
local small_building_schematic = dofile(MP.."/schematics/small_building.lua")
local medium_building_schematic = dofile(MP.."/schematics/medium_building.lua")

local perlin_cave = {
	offset = 0,
	scale = 1,
	spread = {x=200, y=200, z=200},
	seed = 88233498,
	octaves = 6,
	persist = 0.67
}

-- large-scale rise and fall to make the seam between stone and slade less razor-flat
local perlin_wave = {
	offset = 0,
	scale = 1,
	spread = {x=1000, y=1000, z=1000},
	seed = 993455,
	octaves = 3,
	persist = 0.67
}

-- building zones
local perlin_zone = {
	offset = 0,
	scale = 1,
	spread = {x=500, y=500, z=500},
	seed = 199422,
	octaves = 3,
	persist = 0.67
}

local median = df_caverns.config.underworld_level
local floor_mult = 20
local floor_displace = -10
local ceiling_mult = -40
local ceiling_displace = 20
local wave_mult = 50

local y_max = median + 2*wave_mult + ceiling_displace + -2*ceiling_mult
local y_min = median - 2*wave_mult + floor_displace - 2*floor_mult

--df_caverns.config.underworld_min = y_min

--local poisson = mapgen_helper.get_poisson_points({x=-32000, z=-32000}, {x=32000, z=32000}, 1000)
--minetest.debug(dump(poisson.objects))

---------------------------------------------------------
-- Buildings

local oubliette_threshold = 0.8
local town_threshold = 1.1

local local_random = function(x, z)
	local next_seed = math.floor(math.random()*2^21)
	math.randomseed(x + z*2^16)
	local ret = math.random()
	math.randomseed(next_seed)
	return ret
end

-- create a deterministic list of buildings
local get_buildings = function(emin, emax, nvals_zone)
	local buildings = {}
	for x = emin.x, emax.x do
		for z = emin.z, emax.z do

			local index2d = mapgen_helper.index2d(emin, emax, x, z)
			local zone = math.abs(nvals_zone[index2d])

			if zone > oubliette_threshold and zone < town_threshold then
				-- oubliette zone
				--zone = (zone - oubliette_threshold)/(town_threshold-oubliette_threshold) -- turn this into a 0-1 spread
				local building_val = local_random(x, z)
				if building_val > 0.98 then
					building_val = (building_val - 0.98)/0.02
					local building_type
					if building_val < 0.8 then
						building_type = "oubliette"
					elseif building_val < 0.9 then
						building_type = "open oubliette"
					else
						building_type = "lamppost"
					end
					table.insert(buildings,
						{
							pos = {x=x, y=0, z=z}, -- y to be determined later
							building_type = building_type,
							bounding_box = {minpos={x=x-2, z=z-2}, maxpos={x=x+2, z=z+2}},
							priority = math.floor(building_val * 10000000) % 1000, -- indended to allow for deterministic removal of overlapping buildings
						}
					)
				end
			elseif zone > town_threshold then
				-- town zone
				local building_val = local_random(x, z)
				if building_val > 0.9925 then
					building_val = (building_val - 0.9925)/0.0075

					local building_type
					local bounding_box
					local priority = math.floor(building_val * 10000000) % 1000
					local rotation = (priority % 4) * 90

					if building_val < 0.75 then
						building_type = "small building"
						local boundmin, boundmax = mapgen_helper.get_schematic_bounding_box({x=x, y=0, z=z}, small_building_schematic, rotation)
						bounding_box = {minpos=boundmin, maxpos=boundmax}
					elseif building_val < 0.85 then
						building_type = "medium building"
						local boundmin, boundmax = mapgen_helper.get_schematic_bounding_box({x=x, y=0, z=z}, medium_building_schematic, rotation)
						bounding_box = {minpos=boundmin, maxpos=boundmax}
					else
						building_type = "small slab"
						local boundmin, boundmax = mapgen_helper.get_schematic_bounding_box({x=x, y=0, z=z}, small_slab_schematic, rotation)
						bounding_box = {minpos=boundmin, maxpos=boundmax}
					end

					table.insert(buildings,
						{
							pos = {x=x, y=0, z=z}, -- y to be determined later
							building_type = building_type,
							bounding_box = bounding_box,
							rotation = rotation,
							priority = priority, -- indended to allow for deterministic removal of overlapping buildings
						}
					)
				end
			end
		end
	end

	-- eliminate overlapping buildings
	local building_count = table.getn(buildings)
	local overlap_count = 0
	for i = 1, building_count-1 do
		local curr_building = buildings[i]
		for j = i+1, building_count do
			local test_building = buildings[j]
			if test_building ~= nil and curr_building ~= nil and mapgen_helper.intersect_exists_xz(
				curr_building.bounding_box.minpos,
				curr_building.bounding_box.maxpos,
				test_building.bounding_box.minpos,
				test_building.bounding_box.maxpos) then

				if curr_building.priority < test_building.priority then -- this makes elimination of overlapping buildings deterministic
					buildings[i] = nil
					j=building_count+1
				else
					buildings[j] = nil
				end
				overlap_count = overlap_count + 1
			end
		end
	end

	if building_count > 50 and overlap_count > building_count * 2/3 then
		minetest.log("warning", "[df_caverns] underworld mapgen generated " ..
			tostring(building_count) .. " buildings and " .. tostring(overlap_count) ..
			" were eliminated as overlapping, if this happens a lot consider reducing building" ..
			" generation probability to improve efficiency.")
	end

	local compacted_buildings = {}
	for _, building in pairs(buildings) do
		compacted_buildings[minetest.hash_node_position(building.pos)] = building
	end

	return compacted_buildings
end

-----------------------------------------------------------
-- Pits

local radius_pit_max = 40 -- won't actually be this wide, there'll be crystal spires around it
local radius_pit_variance = 10
local plasma_depth_min = 5
local plasma_depth_max = 75

local region_mapblocks = df_caverns.config.underworld_glowing_pit_mapblocks -- One glowing pit in each region this size
local mapgen_chunksize = tonumber(minetest.get_mapgen_setting("chunksize"))
local pit_region_size = region_mapblocks * mapgen_chunksize * 16

local scatter_2d = function(min_xz, gridscale, border_width)
	local bordered_scale = gridscale - 2 * border_width
	local point = {}
	point.x = math.floor(math.random() * bordered_scale + min_xz.x + border_width)
	point.y = 0
	point.z = math.floor(math.random() * bordered_scale + min_xz.z + border_width)
	return point
end

-- For some reason, map chunks generate with -32, -32, -32 as the "origin" minp. To make the large-scale grid align with map chunks it needs to be offset like this.
local get_corner = function(pos)
	return {x = math.floor((pos.x+32) / pit_region_size) * pit_region_size - 32, z = math.floor((pos.z+32) / pit_region_size) * pit_region_size - 32}
end

local mapgen_seed = tonumber(minetest.get_mapgen_setting("seed")) % 2^21

local get_pit = function(pos)
	if region_mapblocks < 1 then return nil end

	local corner_xz = get_corner(pos)
	local next_seed = math.floor(math.random() * 2^21)
	math.randomseed(corner_xz.x + corner_xz.z * 2 ^ 8 + mapgen_seed)
	local location = scatter_2d(corner_xz, pit_region_size, radius_pit_max + radius_pit_variance)
	local variance_multiplier = math.random()
	local radius = variance_multiplier * (radius_pit_max - 15) + 15
	local variance = radius_pit_variance/2 + radius_pit_variance*variance_multiplier/2
	local depth = math.random(plasma_depth_min, plasma_depth_max)
	math.randomseed(next_seed)
	return {location = location, radius = radius, variance = variance, depth = depth}
end

df_caverns.get_nearest_glowing_pit = get_pit

local perlin_pit = {
	offset = 0,
	scale = 1,
	spread = {x=30, y=30, z=30},
	seed = 901,
	octaves = 3,
	persist = 0.67
}

-------------------------------------

minetest.register_chatcommand("find_pit", {
	params = "",
	privs = {server=true},
	decription = "find a nearby glowing pit",
	func = function(name, param)
		local player = minetest.get_player_by_name(name)
		local pit = get_pit(player:get_pos())
		if pit then
			minetest.chat_send_player(name, "Pit location: x=" .. math.floor(pit.location.x) .. " z=" .. math.floor(pit.location.z))
		end
	end,
})

df_caverns.register_biome_check(function(pos, heat, humidity)
	if pos.y > y_max or pos.y < y_min then
		return
	end
	return "underworld"
end)

minetest.register_on_generated(function(minp, maxp, seed)

	--if out of range of cave definition limits, abort
	if minp.y > y_max or maxp.y < y_min then
		return
	end

	local t_start = os.clock()

	local vm, data, data_param2, area = mapgen_helper.mapgen_vm_data_param2()
	local emin = area.MinEdge
	local emax = area.MaxEdge

	local nvals_cave = mapgen_helper.perlin2d("df_caverns:underworld_cave", emin, emax, perlin_cave) --cave noise for structure
	local nvals_wave = mapgen_helper.perlin2d("df_caverns:underworld_wave", emin, emax, perlin_wave) --cave noise for structure
	local nvals_zone = mapgen_helper.perlin2d("df_caverns:underworld_zone", emin, emax, perlin_zone) --building zones

	local pit = get_pit(minp)
	--minetest.chat_send_all(minetest.pos_to_string(pit.location))

	local buildings = get_buildings(emin, emax, nvals_zone)

	local pit_uninitialized = true
	local nvals_pit, area_pit

	for vi, x, y, z in area:iterp_yxz(minp, maxp) do
		if y > y_min then
			local index2d = mapgen_helper.index2d(emin, emax, x, z)
			local abs_cave = math.abs(nvals_cave[index2d]) -- range is from 0 to approximately 2, with 0 being connected and 2s being islands
			local wave = nvals_wave[index2d] * wave_mult

			local floor_height =  math.floor(abs_cave * floor_mult + median + floor_displace + wave)

			if named_waypoints_path and floor_height == y and pit and pit.location.x == x and pit.location.z == z then
				named_waypoints.add_waypoint("glowing_pits", {x=x, y=y, z=z}, {name=name_pit()})
			end

			local underside_height = math.floor(y_min + math.abs(wave) / 5)+2 -- divide wave by five to smooth out the underside of the slade, we only want the interface to ripple a little down here
			local ceiling_height =  math.floor(abs_cave * ceiling_mult + median + ceiling_displace + wave)
			if (y == underside_height or y == underside_height - 1) and (x % 8 == 0 or z % 8 == 0) then
				data[vi] = c_air
			elseif y < floor_height and y > underside_height then
				data[vi] = c_slade
				if	pit and
					pit.location.x - radius_pit_max - radius_pit_variance < maxp.x and
					pit.location.x + radius_pit_max + radius_pit_variance > minp.x and
					pit.location.z - radius_pit_max - radius_pit_variance < maxp.z and
					pit.location.z + radius_pit_max + radius_pit_variance > minp.z
				then
					-- there's a pit nearby
					if pit_uninitialized then
						nvals_pit, area_pit = mapgen_helper.perlin3d("df_cavern:perlin_cave", minp, maxp, perlin_pit)
						pit_uninitialized = false
					end
					local pit_value = nvals_pit[area_pit:index(x,y,z)] * pit.variance
					local distance = vector.distance({x=x, y=y, z=z}, {x=pit.location.x, y=y, z=pit.location.z}) + pit_value
					if distance < pit.radius -2.5 then
						if y < median + floor_displace + wave - pit.depth or y < underside_height + plasma_depth_min then
							data[vi] = c_pit_plasma
							if log_location then log_location("underworld_pit", vector.new(x,y,z)) end
						else
							data[vi] = c_air
						end
					elseif distance < pit.radius then
						data[vi] = c_amethyst
					elseif distance < radius_pit_max and y == floor_height - 4 then
						if math.random() > 0.95 then
							df_underworld_items.underworld_shard(data, area, vi)
						end
					end
				end
			elseif y >= floor_height and y < ceiling_height and data[vi] ~= c_amethyst then
				data[vi] = c_air
			elseif data[vi] == c_water then
				data[vi] = c_air -- no water down here
			end
		end
	end

	-- Ceiling decoration
	for x = minp.x + 1, maxp.x-1 do
		for z = minp.z + 1, maxp.z -1 do
			local index2d = mapgen_helper.index2d(emin, emax, x, z)
			local abs_cave = math.abs(nvals_cave[index2d]) -- range is from 0 to approximately 2, with 0 being connected and 2s being islands
			local wave = nvals_wave[index2d] * wave_mult
			local floor_height = math.floor(abs_cave * floor_mult + median + floor_displace + wave)
			local ceiling_height = math.floor(abs_cave * ceiling_mult + median + ceiling_displace + wave)

			if ceiling_height > floor_height + 5 and ceiling_height < maxp.y and ceiling_height > minp.y then
				local vi = area:index(x, ceiling_height, z)
				if (
					--test if we're nestled in a crevice
					(not mapgen_helper.buildable_to(data[vi-area.ystride + 1]) and not mapgen_helper.buildable_to(data[vi-area.ystride - 1])) or
					(not mapgen_helper.buildable_to(data[vi-area.ystride + area.zstride]) and not mapgen_helper.buildable_to(data[vi-area.ystride - area.zstride]))
				)
				then
					data[vi] = c_glowstone
				end
			end
		end
	end

	-- buildings
	for x = emin.x + 5, emax.x - 5 do
		for z = emin.z + 5, emax.z - 5 do

			local skip = false
			if	pit and
				pit.location.x - radius_pit_max - radius_pit_variance < x and
				pit.location.x + radius_pit_max + radius_pit_variance > x and
				pit.location.z - radius_pit_max - radius_pit_variance < z and
				pit.location.z + radius_pit_max + radius_pit_variance > z
			then
				if vector.distance(pit.location, {x=x, y=0, z=z}) < radius_pit_max + radius_pit_variance then
					-- there's a pit nearby
					skip = true
				end
			end
			if not skip then
				local index2d = mapgen_helper.index2d(emin, emax, x, z)
				local abs_cave = math.abs(nvals_cave[index2d]) -- range is from 0 to approximately 2, with 0 being connected and 2s being islands
				local wave = nvals_wave[index2d] * wave_mult
				local floor_height = math.floor(abs_cave * floor_mult + median + floor_displace + wave)
				local ceiling_height = math.floor(abs_cave * ceiling_mult + median + ceiling_displace + wave)

				if ceiling_height > floor_height and floor_height <= maxp.y and floor_height >= minp.y  then
					local building = buildings[minetest.hash_node_position({x=x,y=0,z=z})]
					if building ~= nil then
						building.pos.y = floor_height
						--minetest.chat_send_all("placing " .. building.building_type .. " at " .. minetest.pos_to_string(building.pos))
						if building.building_type == "oubliette" then
							mapgen_helper.place_schematic_on_data(data, data_param2, area, building.pos, oubliette_schematic)
						elseif building.building_type == "open oubliette" then
							mapgen_helper.place_schematic_on_data(data, data_param2, area, building.pos, oubliette_schematic, 0, {["df_underworld_items:slade_seal"] = "air"})
						elseif building.building_type == "lamppost" then
							mapgen_helper.place_schematic_on_data(data, data_param2, area, building.pos, lamppost_schematic)
							local lamp_pos = vector.new(building.pos.x, building.pos.y+6, building.pos.z)
							minetest.after(math.random()*10, function()
								-- not all of these locations will get a lamp, but it's easier to just set four
								-- timers than to worry about testing first.
								minetest.get_node_timer({x=lamp_pos.x+1, y=lamp_pos.y, z=lamp_pos.z}):start(math.random()*30)
								minetest.get_node_timer({x=lamp_pos.x-1, y=lamp_pos.y, z=lamp_pos.z}):start(math.random()*30)
								minetest.get_node_timer({x=lamp_pos.x, y=lamp_pos.y, z=lamp_pos.z+1}):start(math.random()*30)
								minetest.get_node_timer({x=lamp_pos.x, y=lamp_pos.y, z=lamp_pos.z-1}):start(math.random()*30)
							end)
						elseif building.building_type == "small building" then
							mapgen_helper.place_schematic_on_data(data, data_param2, area, building.pos, small_building_schematic, building.rotation)
							local pos = building.pos
							minetest.after(1, function()
								local puzzle_chest = minetest.find_node_near(pos, 4, {"df_underworld_items:puzzle_chest_closed"}, true)
								if puzzle_chest then
									local def = minetest.registered_nodes["df_underworld_items:puzzle_chest_closed"]
									def.can_dig(puzzle_chest) -- initializes the inventory
									local meta = minetest.get_meta(puzzle_chest)
									local inv = meta:get_inventory()
									for i = 1, math.random(1,8) do
										local item = ItemStack(df_underworld_items.colour_items[math.random(1,#df_underworld_items.colour_items)])
										--item:set_count(math.random(1,4))
										inv:add_item("main", item)
									end
								end
							end)
						elseif building.building_type == "medium building" then
							mapgen_helper.place_schematic_on_data(data, data_param2, area, building.pos, medium_building_schematic, building.rotation)
							if name_generator_path then
								if not next(named_waypoints.get_waypoints_in_area("underworld_ruins", vector.subtract(building.pos, 250), vector.add(building.pos, 250))) then
									named_waypoints.add_waypoint("underworld_ruins", {x=building.pos.x, y=floor_height+1, z=building.pos.z}, {name=name_ruin()})
								end
							end
							if log_location then log_location("underworld_building", building.pos) end
						elseif building.building_type == "small slab" then
							mapgen_helper.place_schematic_on_data(data, data_param2, area, building.pos, small_slab_schematic, building.rotation)
						else
							minetest.log("error", "unrecognized underworld building type: " .. tostring(building.building_type))
						end
					end
				end
			end
		end
	end

	-- puzzle seal
	local puzzle_seal = nil
	if pit_uninitialized and math.random() < 0.05 then
		local index2d = mapgen_helper.index2d(emin, emax, minp.x + 3, minp.z + 3)
		local abs_cave = math.abs(nvals_cave[index2d]) -- range is from 0 to approximately 2, with 0 being connected and 2s being islands
		local wave = nvals_wave[index2d] * wave_mult

		local floor_height =  math.floor(abs_cave * floor_mult + median + floor_displace + wave)
		local underside_height = math.floor(y_min + math.abs(wave) / 5)

		if floor_height < maxp.y and floor_height > minp.y then
			for plat_vi in area:iter(minp.x, floor_height-6, minp.z, minp.x+6, floor_height, minp.z+6) do
				data[plat_vi] = c_slade_block
			end
			puzzle_seal = {x=minp.x+3, y=floor_height+1, z=minp.z+3}
			minetest.log("info", "Puzzle seal generated at " .. minetest.pos_to_string(puzzle_seal))
			if log_location then log_location("underworld_puzzle_seal", puzzle_seal) end
		end
	end

	--send data back to voxelmanip
	vm:set_data(data)
	vm:set_param2_data(data_param2)
	--calc lighting
	vm:set_lighting({day = 0, night = 0})
	vm:calc_lighting()
	vm:update_liquids()
	--write it to world
	vm:write_to_map()

	if puzzle_seal ~= nil then
		if named_waypoints_path then
			named_waypoints.add_waypoint("puzzle_seals", puzzle_seal)
		end

		minetest.place_schematic({x=puzzle_seal.x-3, y=puzzle_seal.y, z=puzzle_seal.z-3}, df_underworld_items.seal_temple_schem, 0, {}, true)
		local node_name = minetest.get_node(puzzle_seal).name
		local node_def = minetest.registered_nodes[node_name]
		node_def.on_construct(puzzle_seal)
	end

	if bones_loot_path then
		for i = 1, 30 do
			local x = math.random(minp.x, maxp.x)
			local z = math.random(minp.z, maxp.z)
			local index2d = mapgen_helper.index2d(emin, emax, x, z)
			local abs_cave = math.abs(nvals_cave[index2d]) -- range is from 0 to approximately 2, with 0 being connected and 2s being islands
			local wave = nvals_wave[index2d] * wave_mult
			local floor_height =  math.floor(abs_cave * floor_mult + median + floor_displace + wave)-1
			local ceiling_height =  math.floor(abs_cave * ceiling_mult + median + ceiling_displace + wave)
			if floor_height < ceiling_height then
				local zone = math.abs(nvals_zone[index2d])
				if math.random() < zone/2 then -- bones are more common in the built-up areas
					local floor_node = minetest.get_node({x=x, y=floor_height, z=z})
					local floor_node_def = minetest.registered_nodes[floor_node.name]
					if floor_node_def and not floor_node_def.buildable_to then
						local y = floor_height + 1
						while y < ceiling_height do
							local target_pos = {x=x, y=y, z=z}
							local target_node = minetest.get_node(target_pos)
							if target_node.name == "air" then
								bones_loot.place_bones(target_pos, "underworld_warrior", math.random(3, 10), nil, true)
								break
							elseif target_node.name == bones_node then
								-- don't stack bones on bones, it looks silly
								break
							end
							y = y + 1
						end
					end
				end
			end
		end
	end

	if hunters_enabled then
		local x = math.random(minp.x, maxp.x)
		local z = math.random(minp.z, maxp.z)
		local index2d = mapgen_helper.index2d(emin, emax, x, z)
		local abs_cave = math.abs(nvals_cave[index2d]) -- range is from 0 to approximately 2, with 0 being connected and 2s being islands
		local wave = nvals_wave[index2d] * wave_mult
		local floor_height =  math.floor(abs_cave * floor_mult + median + floor_displace + wave)-1
		local zone = math.abs(nvals_zone[index2d])
		if math.random() < zone / 4 then -- hunters are more common in the built-up areas. zone/4 gives ~ 400 hunters per square kilometer.
			for y = floor_height, floor_height+20 do
				local target_pos = {x=x, y=y, z=z}
				local target_node = minetest.get_node(target_pos)
				if minetest.get_item_group(target_node.name, "slade") == 0 then
					minetest.set_node(target_pos, {name="df_underworld_items:hunter_statue"})
					break
				end
			end
		end

	end

	local time_taken = os.clock() - t_start -- how long this chunk took, in seconds
	mapgen_helper.record_time("df_caverns underworld", time_taken)
end)


code:
name "glowing_pits" {
    customGroupA = "Actinic, Amethyst, Angry, Atrocious, Bad, Blighted, Baneful, Baleful, Beastly, Calamitous, Corrupt, Crazed, Damnable, Demoniacal, Demonic, Depraved, Destructive, Devilish, Diabolical, Disastrous, Execrable, Fiendish, Foul, Frenzied, Glaring, Harmful, Hateful, Heinous, Hellish, Hideous, Infernal, Iniquitous, Injurious, Loathsome, Lost, Maleficent, Malevolent, Malicious, Malignant, Manic, Nefarious, Nightmare, Obscene, Offensive, Pernicious, Poison, Possessed, Rancorous, Repugnant, Repulsive, Revolting, Spiteful, Unhallowed, Unpleasant, Vicious, Vile, Villainous, Violent, Wicked, Wrathful"

    customGroupB = "Abyss, Aperture, Breach, Cavity, Chasm, Crevasse, Depth, Deep, Fissure, Funnel, Gate, Gulf, Hell, Hole, Hollow, Inferno, Maw, Mouth, Opening, Pit, Portal, Puncture, Ravager, Rent, Rift, Rim, Schism, Shaft, Split, Throat, Void, Well"

    customGroupC = "Adversity, Affliction, Annihilation, Bale, Bane, Blight, Calamity, Cataclysm, Catastrophe, Collapse, Conclusion, Condemnation, Death, Defeat, Destiny, Destruction, Disaster, Doom, Downfall, Failure, Grief, Harm, Hazard, Judgment, Karma, Misadventure, Mischance, Misfortune, Mishap, Ruin, Ruination, Tragedy, Undoing, Verdict, Woe"

    rules = "%50The_$A_$B, The_$B_of_$C, %10The_$A_$B_of_$C"
}

name "underworld_ruins" {

    customGroupA = "Abandoned, Absent, Adrift, Alien, Anonymous, Bare, Barren, Blank, Buried, Clandestine, Cloaked, Concealed, Covered, Cryptic, Dark, Dead, Depleted, Deserted, Desolate, Despoiled, Destitute, Devoid, Disappeared, Distant, Exhausted, Empty, Forfeit, Forfeited, Forsaken, Hidden, Incognito, Indiscernible, Invisible, Irretrievable, Irrevocable, Masked, Mislaid, Misplaced, Mystic, Mystical, Nameless, Obscured, Secluded, Secret, Sequestered, Shadowy, Shrouded, Stark, Strange, Uncelebrated, Uncharted, Undiscovered, Unexplained, Unexplored, Unfamiliar, Unfilled, Unidentified, Unknown, Unnamed, Unredeemed, Unsung, Untold, Vacant, Vacated, Vanished, Veiled, Wayward, Warrior's, King's, Knave's, Coward's, Cardinal's, Priest's, Soldier's, Noble, Steadfast, Children's, Howling, Silent, Grinding, Dusty"

    customGroupB = "Temple, Chapel, House, Sanctuary, Shrine, Fortress, Tomb, Crypt, Graves, Citadel, Garrison, Rampart, Redoubt, Refuge, Asylum, Haven, Hideout, Retreat, Shelter, Stronghold, Covert, Den, Settlement, Preserve, Seat, Watch, Bulwark, Necropolis, Catacomb, Ruin, Hulk, Wreck"

    rules = "The_$A_$B"
}


code:
The DF_Caverns modpack was originally written based on the minetest_game. It made extensive use of various nodes and helper functions that came from minetest_game's constituent mods.

When Mineclone games rose in popularity, the task of making it compatible was a daunting one - there were dependencies on minetest_game's mods scattered everywhere. To make the task manageable, I created this mod to serve as a central location where analogous objects could be taken from those games to be referenced in a generic way.

This requires abusing the "dependencies" system of Minetest, unfortunately. This mod is an enormous collection of either/or dependencies - things that can come from either mod A or mod B, but must come from one of them.


code:
local CONFIG_FILE_PREFIX = "dfcaverns_"

local config = {}
df_dependencies.config = config

local print_settingtypes = false

local function setting(stype, name, default, description)
	local value
	if stype == "bool" then
		value = minetest.settings:get_bool(CONFIG_FILE_PREFIX..name, default)
	elseif stype == "string" then
		value = minetest.settings:get(CONFIG_FILE_PREFIX..name)
	elseif stype == "int" or stype == "float" then
		value = tonumber(minetest.settings:get(CONFIG_FILE_PREFIX..name))
	end
	if value == nil then
		value = default
	end
	config[name] = value

	if print_settingtypes then
		minetest.debug(CONFIG_FILE_PREFIX..name.." ("..description..") "..stype.." "..tostring(default))
	end
end

--Caverns

setting("float", "vertical_cavern_scale", 256, "Vertical cavern dimension scale")
setting("float", "horizontal_cavern_scale", 256, "Horizontal cavern dimension scale")
setting("float", "cavern_threshold", 0.5, "Cavern threshold")
setting("float", "sunless_sea_threshold", 0.4, "Cavern threshold for sunless seas (higher number means sparser caverns)")
setting("float", "tunnel_flooding_threshold", 0.25, "Threshold for flooding tunnels around flooded caverns")

setting("int", "ymax", -193, "Upper limit of level 1")
setting("int", "level1_min", -832, "Upper limit of level 2")
setting("int", "level2_min", -1472, "Upper limit of level 3")
setting("int", "level3_min", -2112, "Upper limit of the sunless seas")
setting("int", "sunless_sea_min", -2512, "Lower limit of the sunless seas")

setting("bool", "enable_oil_sea", true, "Enable oil sea")
config.enable_oil_sea = config.enable_oil_sea and minetest.get_modpath("oil") ~= nil
setting("int", "oil_sea_level", -2700, "Oil sea level")

setting("bool", "enable_lava_sea", true, "Enable magma sea")
setting("int", "lava_sea_level", -2900, "Lava sea level")

setting("bool", "enable_underworld", true, "Enable underworld")
config.enable_underworld = config.enable_underworld and minetest.get_modpath("df_underworld_items") ~= nil
setting("int", "underworld_level", -3200, "Underworld level")
setting("int", "underworld_glowing_pit_mapblocks", 8, "Average pit spacing measured in mapblocks")

setting("bool", "enable_primordial", true, "Enable primordial cavern")
setting("int", "primordial_max", -3393, "Upper limit to primordial caverns")
setting("int", "primordial_min", -4032, "Lower limit to primordial caverns")

setting("bool", "restrict_trees_to_biomes", false, "Restrict underground tree growth to their home biomes")
setting("bool", "restrict_farmables_to_biomes", false, "Restrict underground farmable plant growth to their home biomes")


code:
-- firefly/init.lua

-- Load support for MT game translation.
local S = minetest.get_translator(minetest.get_current_modname())

local bottle = df_dependencies.node_name_glass_bottle

minetest.register_node("df_dependencies:firefly", {
	description = S("Firefly"),
	drawtype = "plantlike",
	tiles = {{
		name = "fireflies_firefly_animated.png",
		animation = {
			type = "vertical_frames",
			aspect_w = 16,
			aspect_h = 16,
			length = 1.5
		},
	}},
	inventory_image = "fireflies_firefly.png",
	wield_image =  "fireflies_firefly.png",
	waving = 1,
	paramtype = "light",
	sunlight_propagates = true,
	buildable_to = true,
	walkable = false,
	groups = {catchable = 1, destroy_by_lava_flow=1},
	selection_box = {
		type = "fixed",
		fixed = {-0.1, -0.1, -0.1, 0.1, 0.1, 0.1},
	},
	light_source = 6,
	floodable = true,
	_mcl_blast_resistance = 0.2,
	_mcl_hardness = 0.2,
	on_place = function(itemstack, placer, pointed_thing)
		local player_name = placer:get_player_name()
		local pos = pointed_thing.above

		if not minetest.is_protected(pos, player_name) and
				not minetest.is_protected(pointed_thing.under, player_name) and
				minetest.get_node(pos).name == "air" then
			minetest.set_node(pos, {name = "df_dependencies:firefly"})
			minetest.get_node_timer(pos):start(1)
			itemstack:take_item()
		end
		return itemstack
	end,
	on_timer = function(pos, elapsed)
		if minetest.get_node_light(pos) > 11 then
			minetest.set_node(pos, {name = "df_dependencies:hidden_firefly"})
		end
		minetest.get_node_timer(pos):start(30)
	end
})

minetest.register_node("df_dependencies:hidden_firefly", {
	description = S("Hidden Firefly"),
	drawtype = "airlike",
	inventory_image = "fireflies_firefly.png^default_invisible_node_overlay.png",
	wield_image =  "fireflies_firefly.png^default_invisible_node_overlay.png",
	paramtype = "light",
	sunlight_propagates = true,
	walkable = false,
	pointable = false,
	diggable = false,
	buildable_to = true,
	drop = "",
	groups = {not_in_creative_inventory = 1, destroy_by_lava_flow=1},
	floodable = true,
	_mcl_blast_resistance = 0.2,
	_mcl_hardness = 0.2,
	on_place = function(itemstack, placer, pointed_thing)
		local player_name = placer:get_player_name()
		local pos = pointed_thing.above

		if not minetest.is_protected(pos, player_name) and
				not minetest.is_protected(pointed_thing.under, player_name) and
				minetest.get_node(pos).name == "air" then
			minetest.set_node(pos, {name = "df_dependencies:hidden_firefly"})
			minetest.get_node_timer(pos):start(1)
			itemstack:take_item()
		end
		return itemstack
	end,
	on_timer = function(pos, elapsed)
		if minetest.get_node_light(pos) <= 11 then
			minetest.set_node(pos, {name = "df_dependencies:firefly"})
		end
		minetest.get_node_timer(pos):start(30)
	end
})

-- bug net
minetest.register_tool("df_dependencies:bug_net", {
	description = S("Bug Net"),
	inventory_image = "fireflies_bugnet.png",
	on_use = function(itemstack, player, pointed_thing)
		local player_name = player and player:get_player_name() or ""
		if not pointed_thing or pointed_thing.type ~= "node" or
				minetest.is_protected(pointed_thing.under, player_name) then
			return
		end
		local node_name = minetest.get_node(pointed_thing.under).name
		local inv = player:get_inventory()
		if minetest.get_item_group(node_name, "catchable") == 1 then
			minetest.set_node(pointed_thing.under, {name = "air"})
			local stack = ItemStack(node_name.." 1")
			local leftover = inv:add_item("main", stack)
			if leftover:get_count() > 0 then
				minetest.add_item(pointed_thing.under, node_name.." 1")
			end
		end
		if not minetest.is_creative_enabled(player_name) then
			itemstack:add_wear_by_uses(256)
			return itemstack
		end
	end
})

minetest.register_craft( {
	output = "df_dependencies:bug_net",
	recipe = {
		{df_dependencies.node_name_string, df_dependencies.node_name_string},
		{df_dependencies.node_name_string, df_dependencies.node_name_string},
		{"group:stick", ""}
	}
})

-- firefly in a bottle
minetest.register_node("df_dependencies:firefly_bottle", {
	description = S("Firefly in a Bottle"),
	inventory_image = "fireflies_bottle.png",
	wield_image = "fireflies_bottle.png",
	tiles = {{
		name = "fireflies_bottle_animated.png",
		animation = {
			type = "vertical_frames",
			aspect_w = 16,
			aspect_h = 16,
			length = 1.5
		},
	}},
	drawtype = "plantlike",
	paramtype = "light",
	sunlight_propagates = true,
	light_source = 9,
	walkable = false,
	groups = {vessel = 1, dig_immediate = 3, attached_node = 1, material_glass = 1, destroy_by_lava_flow=1},
	selection_box = {
		type = "fixed",
		fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25}
	},
	sounds = df_dependencies.sound_glass(),
	on_rightclick = function(pos, node, player, itemstack, pointed_thing)
		local lower_pos = {x = pos.x, y = pos.y + 1, z = pos.z}
		if minetest.is_protected(pos, player:get_player_name()) or
				minetest.get_node(lower_pos).name ~= "air" then
			return
		end

		local upper_pos = {x = pos.x, y = pos.y + 2, z = pos.z}
		local firefly_pos

		if not minetest.is_protected(upper_pos, player:get_player_name()) and
				minetest.get_node(upper_pos).name == "air" then
			firefly_pos = upper_pos
		elseif not minetest.is_protected(lower_pos, player:get_player_name()) then
			firefly_pos = lower_pos
		end

		if firefly_pos then
			minetest.add_item(pos, bottle)
			minetest.set_node(pos, {name = "air"})
			minetest.set_node(firefly_pos, {name = "df_dependencies:firefly"})
			minetest.get_node_timer(firefly_pos):start(1)
		end
	end
})

minetest.register_craft( {
	output = "df_dependencies:firefly_bottle",
	type = "shapeless",
	recipe = {"df_dependencies:firefly", bottle},
})


code:
License of source code
----------------------

The MIT License (MIT)
Copyright (c) 2018 Shara RedCat

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

For more details:
https://opensource.org/licenses/MIT

Licenses of media (textures)
----------------------------

Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
Copyright (C) 2018 Shara RedCat

You are free to:
Share — copy and redistribute the material in any medium or format.
Adapt — remix, transform, and build upon the material for any purpose, even commercially.
The licensor cannot revoke these freedoms as long as you follow the license terms.

Under the following terms:

Attribution — You must give appropriate credit, provide a link to the license, and
indicate if changes were made. You may do so in any reasonable manner, but not in any way
that suggests the licensor endorses you or your use.

ShareAlike — If you remix, transform, or build upon the material, you must distribute
your contributions under the same license as the original.

No additional restrictions — You may not apply legal terms or technological measures that
legally restrict others from doing anything the license permits.

Notices:

You do not have to comply with the license for elements of the material in the public
domain or where your use is permitted by an applicable exception or limitation.
No warranties are given. The license may not give you all of the permissions necessary
for your intended use. For example, other rights such as publicity, privacy, or moral
rights may limit how you use the material.

For more details:
http://creativecommons.org/licenses/by-sa/3.0/


code:
local S = minetest.get_translator(minetest.get_current_modname())

if minetest.get_modpath("default") then
	df_dependencies.register_leafdecay = default.register_leafdecay
	df_dependencies.after_place_leaves = default.after_place_leaves
elseif ("mcl_core") then
	-- Mineclone does leaf decay differently, it uses the "leafdecay" group to require that leaves remain
	-- within the group value distance of any node of group "tree".
	-- make sure to add place_param2 = 1 to leaves to prevent decay of player-placed leaves
	df_dependencies.register_leafdecay = function() end
	df_dependencies.after_place_leaves = function() end
end

df_dependencies.mods_required.bucket = true
df_dependencies.mods_required.mcl_buckets = true
if minetest.get_modpath("bucket") then
	df_dependencies.bucket_register_liquid = bucket.register_liquid
elseif minetest.get_modpath("mcl_buckets") then
	df_dependencies.bucket_register_liquid = function(source_liquid, flowing_liquid, bucket_node, texture, desc)
		mcl_buckets.register_liquid({
			bucketname = bucket_node,
			source_take = {source_liquid},
			source_place = source_liquid,
			inventory_image = texture,
			name = desc})
		end
end

-- Note that a circular table reference will result in a crash, TODO: guard against that.
-- Unlikely to be needed, though - it'd take a lot of work for users to get into this bit of trouble.
local function deep_copy(table_in)
	local table_out = {}

	for index, value in pairs(table_in) do
		if type(value) == "table" then
			table_out[index] = deep_copy(value)
		else
			table_out[index] = value
		end
	end
	return table_out
end

df_dependencies.mods_required.stairs = true
df_dependencies.mods_required.moreblocks = true
df_dependencies.mods_required.doors = true
df_dependencies.mods_required.mcl_stairs = true
df_dependencies.mods_required.mcl_fences = true
df_dependencies.mods_required.mcl_core = true

local node_name_to_stair_properties = function(name, override_def)
	local mod = minetest.get_current_modname()

	local node_def = minetest.registered_nodes[mod..":"..name]
	override_def = override_def or {}

	local node_copy = deep_copy(node_def)
	for index, value in pairs(override_def) do
		node_copy[index] = value
	end
	return mod, node_copy
end

df_dependencies.register_stairs = function(name, override_def)
	local mod, node_copy = node_name_to_stair_properties(name, override_def)
	if minetest.get_modpath("stairs") and stairs.register_stair_and_slab then
		stairs.register_stair_and_slab(
			name,
			mod ..":" .. name,
			node_copy.groups,
			node_copy.tiles,
			S("@1 Stair", node_copy.description),
			S("@1 Slab", node_copy.description),
			node_copy.sounds
		)
	end
	if minetest.get_modpath("mcl_stairs") and mcl_stairs.register_stair_and_slab_simple then
		mcl_stairs.register_stair_and_slab_simple(
			name,
			mod ..":" .. name,
			S("@1 Stair", node_copy.description),
			S("@1 Slab", node_copy.description),
			S("Double @1 Slab", node_copy.description)
		)
	end
end

df_dependencies.register_more_stairs = function(name, override_def)
	local mod, node_copy = node_name_to_stair_properties(name, override_def)
	if minetest.get_modpath("moreblocks") and stairsplus.register_all then
		stairsplus:register_all(mod, name, mod..":"..name, node_copy)
	end
end

df_dependencies.register_all_fences = function (name, override_def)
	local mod, node_def = node_name_to_stair_properties(name, override_def)

	override_def = override_def or {}

	local material = override_def.material or mod..":"..name
	local burntime = override_def.burntime
	local texture = override_def.texture or node_def.tiles[1]

	if minetest.get_modpath("default") then
		if default.register_fence then
			default.register_fence(material .. "_fence", {
				description = S("@1 Fence", node_def.description),
				texture = texture,
				material = material,
				groups = deep_copy(node_def.groups or {}), -- the default register_fence function modifies the groups table passed in, so send a copy instead to be on the safe side.
				sounds = node_def.sounds
			})
			if burntime then
				minetest.register_craft({
					type = "fuel",
					recipe = material .. "_fence",
					burntime = burntime, -- ignoring two sticks
				})
			end
		end

		if default.register_fence_rail then
			default.register_fence_rail(material .. "_fence_rail", {
				description = S("@1 Fence Rail", node_def.description),
				texture = texture,
				material = material,
				groups = deep_copy(node_def.groups or {}), -- the default register_fence_rail function modifies the groups table passed in, so send a copy instead to be on the safe side.
				sounds = node_def.sounds
			})
			if burntime then
				minetest.register_craft({
					type = "fuel",
					recipe = material .. "_fence_rail",
					burntime = burntime * 4/16,
				})
			end
		end

		if default.register_mesepost then
			default.register_mesepost(material .. "_mese_light", {
				description = S("@1 Mese Post Light", node_def.description),
				texture = texture,
				material = material,
				groups = deep_copy(node_def.groups or {}), -- the default register_fence_rail function modifies the groups table passed in, so send a copy instead to be on the safe side.
				sounds = node_def.sounds
			})
		end
	end

	if minetest.get_modpath("doors") and doors.register_fencegate then
		doors.register_fencegate(material .. "_fence_gate", {
			description = S("@1 Fence Gate", node_def.description),
			texture = texture,
			material = material,
			groups = deep_copy(node_def.groups or {}), -- the default register_fence_rail function modifies the groups table passed in, so send a copy instead to be on the safe side.
			sounds = node_def.sounds
		})

		if burntime then
			minetest.register_craft({
				type = "fuel",
				recipe = material .. "_fence_gate_closed",
				burntime = burntime * 2, -- ignoring four sticks
			})
		end
	end

	if minetest.get_modpath("mcl_fences") and mcl_fences.register_fence_and_fence_gate then
		local groups = deep_copy(node_def.groups or {})
		groups.fence_wood = 1
		mcl_fences.register_fence_and_fence_gate(name .. "_fence",
			S("@1 Fence", node_def.description),
			S("@1 Fence Gate", node_def.description),
			texture,
			groups,
			node_def._mcl_hardness or minetest.registered_nodes["mcl_core:wood"]._mcl_hardness,
			node_def._mcl_blast_resistance or minetest.registered_nodes["mcl_core:wood"]._mcl_blast_resistance,
			{"group:fence_wood"}
		)
	end
end

df_dependencies.register_all_stairs_and_fences = function(name, override_def)
	df_dependencies.register_stairs(name, override_def)
	df_dependencies.register_more_stairs(name, override_def)
	df_dependencies.register_all_fences(name, override_def)
end

df_dependencies.mods_required.tnt = true
df_dependencies.mods_required.mcl_explosions = true

if minetest.get_modpath("tnt") then
	df_dependencies.tnt_boom = tnt.boom
elseif minetest.get_modpath("mcl_explosions") then
	df_dependencies.tnt_boom = function(pos, def)
		local strength = def.radius * 5
		mcl_explosions.explode(pos, strength)
	end
end

df_dependencies.mods_required.mcl_formspec = true
df_dependencies.get_itemslot_bg = function(X,Y,W,H) return "" end
df_dependencies.get_itemslot_bg_padded = function(X,Y,W,H) return "" end
if minetest.get_modpath("mcl_formspec") then
	df_dependencies.get_itemslot_bg = mcl_formspec.get_itemslot_bg

	df_dependencies.get_itemslot_bg_padded = function(x,y,w,h, padding)
		local out = ""
		for i = 0, w - 1, 1 do
			for j = 0, h - 1, 1 do
				out = out .. mcl_formspec.get_itemslot_bg(x+i+(i*padding), y+j+(j*padding), 1, 1)
			end
		end
		return out
	end
end


code:
df_dependencies = {}
local modpath = minetest.get_modpath(minetest.get_current_modname())

df_dependencies.mods_required = {}

df_dependencies.select_required = function(def)
	local ret
	for _, dependency in ipairs(def) do
		mod = dependency[1]
		item = dependency[2]
		df_dependencies.mods_required[mod] = true
		if minetest.get_modpath(mod) then
			ret = item
		end
	end
	assert(ret, "Unable to find item for dependency set " .. dump(def))
	return ret
end

df_dependencies.select_optional = function(def)
	local ret
	for _, dependency in ipairs(def) do
		mod = dependency[1]
		item = dependency[2]
		df_dependencies.mods_required[mod] = true
		if minetest.get_modpath(mod) then
			ret = item
		end
	end
	return ret
end

dofile(modpath.."/config.lua")
dofile(modpath.."/sounds.lua")
dofile(modpath.."/helper_functions.lua")
dofile(modpath.."/nodes.lua")
dofile(modpath.."/misc.lua")
dofile(modpath.."/mapgen.lua")

local list_mods_required = function()
	local mods_required = ""
	local mods_sorted = {}
	for mod, _ in pairs(df_dependencies.mods_required) do
		table.insert(mods_sorted, mod)
	end
	table.sort(mods_sorted)
	for _, mod in ipairs(mods_sorted) do
		mods_required = mods_required .. ", " .. mod
	end
	minetest.debug(mods_required)
end
--list_mods_required()

-- This mod is meant to only exist at initialization time. Other mods should make copies of anything it points to for their own use.
minetest.after(1, function() df_dependencies = nil end)


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Insektennetz
# textdomain: fireflies
Firefly=Glühwürmchen
Firefly in a Bottle=Glühwürmchen in einer Flasche
Hidden Firefly=Verborgenes Glühwürmchen

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
@1 Slab=@1 Platte
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
@1 Stair=@1 Treppe
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Insekta Reto
# textdomain: fireflies
Firefly=Lampiro
Firefly in a Bottle=Lampiro en Botelo
Hidden Firefly=Kaŝita Lampiro

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Red de insectos
# textdomain: fireflies
Firefly=Luciérnaga
Firefly in a Bottle=Luciérnaga en botella
Hidden Firefly=Luciérnaga oculta

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Filet à papillon
# textdomain: fireflies
Firefly=Luciole
Firefly in a Bottle=Luciole en bouteille
Hidden Firefly=Luciole cachée

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Jaring Serangga
# textdomain: fireflies
Firefly=Kunang-Kunang
Firefly in a Bottle=Kunang-Kunang dalam Botol
Hidden Firefly=Kunang-Kunang Tersembunyi

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Retino
# textdomain: fireflies
Firefly=Lucciola
Firefly in a Bottle=Lucciola imbottigliata
Hidden Firefly=Lucciola nascosta

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
@1 Slab=@1 Lastra
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
@1 Stair=@1 Scala
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=Cappello del Nether


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=虫取り網
# textdomain: fireflies
Firefly=ホタル
Firefly in a Bottle=ホタルの入った瓶
Hidden Firefly=隠されたホタル

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=lo cinki julne
# textdomain: fireflies
Firefly=lo gusycinki
Firefly in a Bottle=lo gusycinki poi nenri lo botpi
Hidden Firefly=lo se mipri gusycinki

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Jaring Pepijat
# textdomain: fireflies
Firefly=Kelip-Kelip
Firefly in a Bottle=Kelip-Kelip dalam Botol
Hidden Firefly=Kelip-Kelip Tersembunyi

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Siatka na owady
# textdomain: fireflies
Firefly=Świetlik
Firefly in a Bottle=Świetlik w butelce
Hidden Firefly=Ukryty świetlik

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Rede de Insetos
# textdomain: fireflies
Firefly=Vaga-lume
Firefly in a Bottle=Vaga-lume em uma garrafa
Hidden Firefly=Vaga-lume escondido

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Сачок Для Ловли Насекомых
# textdomain: fireflies
Firefly=Светлячок
Firefly in a Bottle=Светлячок в Бутылке
Hidden Firefly=Притаившийся Светлячок

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Sieťka na hmyz
# textdomain: fireflies
Firefly=Svetluška
Firefly in a Bottle=Svetluška vo fľaši
Hidden Firefly=Skrytá svetluška

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Insektshåv
# textdomain: fireflies
Firefly=Eldfluga
Firefly in a Bottle=Eldfluga i en flaska
Hidden Firefly=Gömd eldfluga

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=Сачок Для Ловлі Комах
# textdomain: fireflies
Firefly=Світлячок
Firefly in a Bottle=Світлячок у Пляшці
Hidden Firefly=Світлячок, що причаївся

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=虫网
# textdomain: fireflies
Firefly=萤火虫
Firefly in a Bottle=放在瓶子里的萤火虫
Hidden Firefly=隐藏的萤火虫

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=蟲網
# textdomain: fireflies
Firefly=螢火蟲
Firefly in a Bottle=放在瓶子裡的螢火蟲
Hidden Firefly=隱藏的螢火蟲

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=


code:
# textdomain: df_dependencies

### fireflies.lua ###

Bug Net=
Firefly=
Firefly in a Bottle=
Hidden Firefly=

### helper_functions.lua ###

@1 Fence=
@1 Fence Gate=
@1 Fence Rail=
@1 Mese Post Light=
@1 Slab=
@1 Stair=
Double @1 Slab=

### misc.lua ###

Icecap=
Nethercap=