code:
if minetest.get_modpath("dungeon_loot") then

if df_caverns.config.enable_underworld then
	dungeon_loot.register({
		{name = "df_underworld_items:glow_amethyst", chance = 0.3, count = {1, 12}, y = {-32768, df_caverns.config.lava_sea_level}},
	})
end

if df_caverns.config.enable_oil_sea and minetest.get_modpath("bucket") then
	dungeon_loot.register({
		{name = "oil:oil_bucket", chance = 0.5, count = {1, 3}, y = {-32768, df_caverns.config.ymax}},
	})
end

if df_caverns.config.enable_lava_sea then
	dungeon_loot.register({
		{name = "df_mapitems:mese_crystal", chance = 0.25, count = {1, 5}, y = {-32768, df_caverns.config.sunless_sea_min}},
		{name = "df_mapitems:glow_mese", chance = 0.1, count = {1, 3}, y = {-32768, df_caverns.config.sunless_sea_min}},
	})
end

dungeon_loot.register({
	{name = "df_farming:cave_wheat_seed", chance = 0.5, count = {1, 10}, y = {-32768, df_caverns.config.ymax}},
	{name = "df_farming:cave_bread", chance = 0.8, count = {1, 10}, y = {-32768, df_caverns.config.ymax}},
	{name = "df_farming:pig_tail_thread", chance = 0.7, count = {1, 10}, y = {-32768, df_caverns.config.ymax}},
	{name = "df_farming:plump_helmet_spawn", chance = 0.4, count = {1, 8}, y = {-32768, df_caverns.config.ymax}},
	{name = "df_farming:plump_helmet_4_picked", chance = 0.8, count = {1, 15}, y = {-32768, df_caverns.config.ymax}},
	{name = "df_trees:glowing_bottle_red", chance = 0.6, count = {1, 20}, y = {-32768, df_caverns.config.ymax}},
	{name = "df_trees:glowing_bottle_green", chance = 0.5, count = {1, 20}, y = {-32768, df_caverns.config.ymax}},
	{name = "df_trees:glowing_bottle_cyan", chance = 0.4, count = {1, 15}, y = {-32768, df_caverns.config.ymax}},
	{name = "df_trees:glowing_bottle_golden", chance = 0.3, count = {1, 5}, y = {-32768, df_caverns.config.ymax}},

	{name = "df_farming:pig_tail_seed", chance = 0.5, count = {1, 10}, y = {-32768, df_caverns.config.level1_min}},
	{name = "df_mapitems:med_crystal", chance = 0.2, count = {1, 2}, y = {-32768, df_caverns.config.level1_min}},

	{name = "df_farming:dimple_cup_seed", chance = 0.3, count = {1, 10}, y = {-32768, df_caverns.config.level2_min}},
	{name = "df_farming:quarry_bush_seed", chance = 0.3, count = {1, 5}, y = {-32768, df_caverns.config.level2_min}},
	{name = "df_farming:sweet_pod_seed", chance = 0.3, count = {1, 5}, y = {-32768, df_caverns.config.level2_min}},
	{name = "df_mapitems:big_crystal", chance = 0.1, count = {1, 1}, y = {-32768, df_caverns.config.level2_min}},
	{name = "df_trees:torchspine_ember", chance = 0.3, count = {1, 3}, y = {-32768, df_caverns.config.level2_min}},
	{name = "ice_sprites:ice_sprite_bottle", chance = 0.1, count = {1, 1}, y = {-32768, df_caverns.config.level2_min}},
})

end

if minetest.get_modpath("bones_loot") and df_caverns.config.enable_underworld then

if minetest.get_modpath("default") then
bones_loot.register_loot({
	{name = "binoculars:binoculars", chance = 0.05, count = {1,1}, types = {"underworld_warrior"}},
	{name = "boats:boat", chance = 0.05, count = {1,1}, types = {"underworld_warrior"}},
	{name = "mesecraft_bucket:bucket_empty", chance = 0.3, count = {1,1}, types = {"underworld_warrior"}},
	{name = "fire:flint_and_steel", chance = 0.3, count = {1,1}, types = {"underworld_warrior"}},
	{name = "flowers:tulip_black", chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "flowers:dandelion_white", chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "flowers:dandelion_yellow", chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "flowers:rose", chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "flowers:tulip", chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "flowers:chrysanthemum_green", chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "flowers:geranium", chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "map:mapping_kit", chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "screwdriver:screwdriver", chance = 0.05, count = {1,1}, types = {"underworld_warrior"}},
	-- don't give the player tnt:tnt, they can craft that from this if tnt is enabled for them
	{name = "tnt:gunpowder", chance = 0.4, count = {1,10}, types = {"underworld_warrior"}},
	{name = "tnt:tnt_stick", chance = 0.3, count = {1,6}, types = {"underworld_warrior"}},

	{name = "vessels:steel_bottle", chance = 0.4, count = {1,3}, types = {"underworld_warrior"}},
	{name = "vessels:glass_bottle", chance = 0.2, count = {1,2}, types = {"underworld_warrior"}},
	{name = "vessels:glass_fragments", chance = 0.1, count = {1,4}, types = {"underworld_warrior"}},

	{name = "default:book", chance = 0.05, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:paper", chance = 0.1, count = {1,6}, types = {"underworld_warrior"}},
	{name = "default:skeleton_key", chance = 0.05, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:torch", chance = 0.75, count = {1,10}, types = {"underworld_warrior"}},

	{name = "default:pick_bronze",    chance = 0.15, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:pick_steel",     chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:pick_mese",      chance = 0.05, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:pick_diamond",   chance = 0.05, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:shovel_bronze",  chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:shovel_steel",   chance = 0.05, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:shovel_mese",    chance = 0.025, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:shovel_diamond", chance = 0.025, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:axe_bronze",     chance = 0.3, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:axe_steel",      chance = 0.5, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:axe_mese",       chance = 0.15, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:axe_diamond",    chance = 0.15, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:sword_bronze",   chance = 0.5, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:sword_steel",    chance = 0.75, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:sword_mese",     chance = 0.35, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:sword_diamond",  chance = 0.35, count = {1,1}, types = {"underworld_warrior"}},

	{name = "default:coal_lump",             chance = 0.5, count = {1,5}, types = {"underworld_warrior"}},
	{name = "default:mese_crystal",          chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:diamond",               chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:steel_ingot",           chance = 0.2, count = {1,3}, types = {"underworld_warrior"}},
	{name = "default:copper_ingot",          chance = 0.1, count = {1,2}, types = {"underworld_warrior"}},
	{name = "default:bronze_ingot",          chance = 0.2, count = {1,5}, types = {"underworld_warrior"}},
	{name = "default:gold_ingot",            chance = 0.3, count = {1,3}, types = {"underworld_warrior"}},
	{name = "default:mese_crystal_fragment", chance = 0.4, count = {1,5}, types = {"underworld_warrior"}},
	{name = "default:obsidian_shard",        chance = 0.4, count = {1,3}, types = {"underworld_warrior"}},
	{name = "default:flint",                 chance = 0.3, count = {1,1}, types = {"underworld_warrior"}},
	{name = "default:sign_wall_wood",  chance = 0.1, count = {1,4}, types = {"underworld_warrior"}},
	{name = "default:sign_wall_steel", chance = 0.1, count = {1,2}, types = {"underworld_warrior"}},
	{name = "default:ladder_wood",     chance = 0.5, count = {1,10}, types = {"underworld_warrior"}},
	{name = "default:ladder_steel",    chance = 0.2, count = {1,5}, types = {"underworld_warrior"}},
	{name = "default:meselamp",        chance = 0.1, count = {1,2}, types = {"underworld_warrior"}},
	{name = "default:mese_post_light", chance = 0.25, count = {1,5}, types = {"underworld_warrior"}},
})
end

if minetest.get_modpath("mcl_core") then
bones_loot.register_loot({
	-- Taken from the ruined portal loot table in mcl
	{name = "mcl_core:iron_nugget",            types = {"underworld_warrior"}, chance = 40/50, count ={9, 18}},
	{name = "mcl_core:flint",                  types = {"underworld_warrior"}, chance = 40/50, count ={9, 18}},
	{name = "mcl_core:obsidian",               types = {"underworld_warrior"}, chance = 40/50, count ={1,  2}},
	{name = "mcl_fire:fire_charge",            types = {"underworld_warrior"}, chance = 40/50, count ={1,  1}},
	{name = "mcl_fire:flint_and_steel",        types = {"underworld_warrior"}, chance = 40/50, count ={1,  1}},
	{name = "mcl_core:gold_nugget",            types = {"underworld_warrior"}, chance = 15/50, count ={4, 24}},
	{name = "mcl_core:apple_gold",             types = {"underworld_warrior"}, chance = 15/50, count ={1, 1}},
	{name = "mcl_tools:axe_gold",              types = {"underworld_warrior"}, chance = 15/50, count ={1, 1}},
	{name = "mcl_farming:hoe_gold",            types = {"underworld_warrior"}, chance = 15/50, count ={1, 1}},
	{name = "mcl_tools:pick_gold",             types = {"underworld_warrior"}, chance = 15/50, count ={1, 1}},
	{name = "mcl_tools:shovel_gold",           types = {"underworld_warrior"}, chance = 15/50, count ={1, 1}},
	{name = "mcl_tools:sword_gold",            types = {"underworld_warrior"}, chance = 15/50, count ={1, 1}},
	{name = "mcl_armor:helmet_gold",           types = {"underworld_warrior"}, chance = 15/50, count ={1, 1}},
	{name = "mcl_armor:chestplate_gold",       types = {"underworld_warrior"}, chance = 15/50, count ={1, 1}},
	{name = "mcl_armor:leggings_gold",         types = {"underworld_warrior"}, chance = 15/50, count ={1, 1}},
	{name = "mcl_armor:boots_gold",            types = {"underworld_warrior"}, chance = 15/50, count ={1, 1}},
	{name = "mcl_potions:speckled_melon",      types = {"underworld_warrior"}, chance =  5/50, count ={4, 12}},
	{name = "mcl_farming:carrot_item_gold",    types = {"underworld_warrior"}, chance =  5/50, count ={4, 12}},
	{name = "mcl_core:gold_ingot",             types = {"underworld_warrior"}, chance =  5/50, count ={2,  8}},
	{name = "mcl_clock:clock",                 types = {"underworld_warrior"}, chance =  5/50, count ={1,1}},
	{name = "mesecons_pressureplates:pressure_plate_gold_off", types = {"underworld_warrior"}, chance =  5/50, count ={1,1}},
	{name = "mobs_mc:gold_horse_armor",        types = {"underworld_warrior"}, chance =  5/50, count ={1,1}},
	{name = "mcl_bells:bell",                  types = {"underworld_warrior"}, chance =  1/50, count ={1,1}},
	{name = "mcl_core:apple_gold_enchanted",   types = {"underworld_warrior"}, chance =  1/50, count ={1,1}},
})

for flower_name, _ in pairs(mcl_flowers.registered_simple_flowers) do
	bones_loot.register_loot({
		{name = flower_name, chance = 0.1, count = {1,1}, types = {"underworld_warrior"}},
	})

end

end

bones_loot.register_loot({
	{name = "ice_sprites:ice_sprite_bottle", chance = 0.025, count = {1, 1}, types = {"underworld_warrior"}},
	{name = "df_underworld_items:glow_amethyst", chance = 0.25, count = {1, 2}, types = {"underworld_warrior"}},
})

if df_caverns.config.enable_lava_sea then
	bones_loot.register_loot({name = "df_mapitems:mese_crystal", chance = 0.25, count = {1, 2}, types = {"underworld_warrior"}})
end

end


code:
local add_biome_restrictions = function(root_table, function_name, biome_set)
	local old_function = root_table[function_name]
	root_table[function_name] = function(pos)
		local biome = df_caverns.get_biome(pos)
		return old_function(pos) and biome_set[biome]
	end
end

if df_caverns.config.restrict_trees_to_biomes then

	if minetest.get_modpath("df_trees") then
		add_biome_restrictions(df_trees, "black_cap_growth_permitted", {blackcap = true})
		add_biome_restrictions(df_trees, "blood_thorn_growth_permitted", {bloodthorn = true})
		add_biome_restrictions(df_trees, "fungiwood_growth_permitted", {fungiwood = true, fungispore = true})
		add_biome_restrictions(df_trees, "goblin_cap_growth_permitted", {goblincap = true, towergoblin = true})
		add_biome_restrictions(df_trees, "nether_cap_growth_permitted", {nethercap = true})
		add_biome_restrictions(df_trees, "spore_tree_growth_permitted", {sporetree = true, fungispore = true})
		add_biome_restrictions(df_trees, "tower_cap_growth_permitted", {towercap = true, towergoblin = true})
		add_biome_restrictions(df_trees, "tunnel_tube_growth_permitted", {tunneltube = true})
		-- Deliberately not biome-restricted
		--add_biome_restrictions(df_trees, "torchspine_growth_permitted", {})
		--add_biome_restrictions(df_trees, "spindlestem_growth_permitted", {})
	end

	if minetest.get_modpath("df_primordial_items") then
		add_biome_restrictions(df_primordial_items, "primordial_mushroom_growth_permitted", {["primordial fungus"] = true})
		add_biome_restrictions(df_primordial_items, "giant_mycelium_growth_permitted", {["primordial fungus"] = true})
		add_biome_restrictions(df_primordial_items, "giant_fern_growth_permitted", {["primordial jungle"] = true})
		add_biome_restrictions(df_primordial_items, "jungle_mushroom_growth_permitted", {["primordial jungle"] = true})
		add_biome_restrictions(df_primordial_items, "jungletree_growth_permitted", {["primordial jungle"] = true})
	end

end

if df_caverns.config.restrict_farmables_to_biomes and minetest.get_modpath("df_farming") then
	add_biome_restrictions(df_farming.growth_permitted, "df_farming:cave_wheat_seed",
		{fungiwood = true, tunneltube = true, sporetree = true, fungispore = true})
	add_biome_restrictions(df_farming.growth_permitted, "df_farming:dimple_cup_seed",
		{towergoblin = true})
	add_biome_restrictions(df_farming.growth_permitted, "df_farming:pig_tail_seed",
		{sporetree = true, fungispore = true})
	add_biome_restrictions(df_farming.growth_permitted, "df_farming:quarry_bush_seed",
		{bloodthorn = true})
	add_biome_restrictions(df_farming.growth_permitted, "df_farming:sweet_pod_seed",
		{tunneltube = true, fungispore = true})
	add_biome_restrictions(df_farming.growth_permitted, "df_farming:plump_helmet_spawn",
		{fungiwood = true, towercap = true, goblincap = true, towergoblin = true})
end

repo_name: korkut/MeseCraft

license: mit


code:
df_caverns = {}
df_caverns.config = df_dependencies.config

--grab a shorthand for the filepath of the mod
local modpath = minetest.get_modpath(minetest.get_current_modname())

--load companion lua files
dofile(modpath.."/node_ids.lua")

dofile(modpath.."/shared.lua")
dofile(modpath.."/surface_tunnels.lua")
dofile(modpath.."/level1.lua")
dofile(modpath.."/level2.lua")
dofile(modpath.."/level3.lua")
dofile(modpath.."/sunless_sea.lua")
dofile(modpath.."/oil_sea.lua")
dofile(modpath.."/lava_sea.lua")
dofile(modpath.."/underworld.lua")
dofile(modpath.."/primordial.lua")
dofile(modpath.."/dungeon_loot.lua")
dofile(modpath.."/growth_restrictions.lua")


code:
if not df_caverns.config.enable_lava_sea then
	return
end

local c_air = df_caverns.node_id.air
local c_lava = df_caverns.node_id.lava
local c_meseore = df_caverns.node_id.meseore
local c_mese_crystal = df_caverns.node_id.mese_crystal
local c_mese_crystal_block = df_caverns.node_id.mese_crystal_block
local c_obsidian = df_caverns.node_id.obsidian

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

local perlin_cave = {
	offset = 0,
	scale = 1,
	spread = {x=100, y=100, z=100},
	seed = -787324,
	octaves = 3,
	persist = 0.67
}

-- large-scale rise and fall
local perlin_wave = {
	offset = 0,
	scale = 1,
	spread = {x=1000, y=1000, z=1000},
	seed = 256664,
	octaves = 3,
	persist = 0.67
}

local perlin_mese = {
	offset = 0,
	scale = 1,
	spread = {x=500, y=500, z=500},
	seed = -50001,
	octaves = 3,
	persist = 0.67
}

local median = df_caverns.config.lava_sea_level
local floor_mult = 60
local floor_displace = -25
local ceiling_mult = -40
local ceiling_displace = 15
local wave_mult = 10

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

df_caverns.register_biome_check(function(pos, heat, humidity)
	if pos.y > y_max or pos.y < y_min then
		return
	end
	return "lava_sea"
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()

	math.randomseed(minp.x + minp.y*2^8 + minp.z*2^16 + seed) -- make decorations consistent between runs

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

	local nvals_cave = mapgen_helper.perlin2d("df_caverns:lava_cave", minp, maxp, perlin_cave)
	local nvals_wave = mapgen_helper.perlin2d("df_caverns:lava_wave", minp, maxp, perlin_wave)
	local nvals_mese = mapgen_helper.perlin2d("df_caverns:lava_mese", minp, maxp, perlin_mese)
	local nvals_lavasurface = mapgen_helper.perlin2d("df_cavern:cracks", minp, maxp, df_caverns.np_cracks)

	for vi, x, y, z in area:iterp_yxz(minp, maxp) do
		local index2d = mapgen_helper.index2d(minp, maxp, 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 lava = nvals_lavasurface[index2d]

		local floor_height = math.floor(abs_cave * floor_mult + floor_displace + median + wave)
		local ceiling_height = math.floor(abs_cave * ceiling_mult + median + ceiling_displace + wave)
		local lava_height = math.floor(median + lava * 2)

		if y >= floor_height - 2 and y >= ceiling_height and y < ceiling_height + 2 and y <= lava_height + 2 and not mapgen_helper.buildable_to(data[vi]) then
			data[vi] = c_obsidian -- obsidian ceiling
		elseif y > floor_height and y < ceiling_height then
			if y > lava_height then
				data[vi] = c_air
			else
				data[vi] = c_lava
				if log_location then log_location("magma_sea", area:position(vi)) end
			end
		elseif y > floor_height - 5 and y < ceiling_height and y <= lava_height + 2 and not mapgen_helper.buildable_to(data[vi]) then
			data[vi] = c_obsidian -- thick obsidian floor
		elseif y < lava_height and data[vi] == c_air then
			data[vi] = c_lava
		end
	end

	-- decoration loop.
	for x = minp.x + 1, maxp.x-1 do
		for z = minp.z + 1, maxp.z -1 do
			local index2d = mapgen_helper.index2d(minp, maxp, x, z)
			local mese_intensity = math.abs(nvals_mese[index2d])

			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 + floor_displace + median + wave)
			local ceiling_height = math.floor(abs_cave * ceiling_mult + median + ceiling_displace + wave)

			local lava = nvals_lavasurface[index2d]
			local lava_height = math.floor(median + lava * 2)

			if mese_intensity > 0.65 and ceiling_height > lava_height + 1 and ceiling_height > floor_height + 1 and ceiling_height <= maxp.y and ceiling_height >= minp.y then
				local vi = area:index(x, ceiling_height, z)
				if not mapgen_helper.buildable_to(data[vi]) then
					-- decorate ceiling
					if math.random() < 0.25 then
						data[vi] = c_meseore
						if log_location then log_location("magma_sea_meseore", area:position(vi)) end
					elseif mese_intensity > 0.75 and math.random() < 0.1 then
						data[vi] = c_meseore
						local bi = vi-area.ystride
						data[bi] = c_mese_crystal
						data_param2[bi] = math.random(1,4) + 19
						if log_location then log_location("magma_sea_mesecrystal", area:position(vi)) end
					elseif mese_intensity > 0.85 and math.random() < 0.025 then
						subterrane.big_stalactite(vi-area.ystride, area, data, 6, 13, c_meseore, c_meseore, c_mese_crystal_block)
						if log_location then log_location("magma_sea_mesebig", area:position(vi)) end
					end
				end
			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()

	local time_taken = os.clock() - t_start -- how long this chunk took, in seconds
	mapgen_helper.record_time("df_caverns lava sea", time_taken)
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_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_spindlestem_white = df_caverns.node_id.spindlestem_white

local tower_cap_shrublist
local fungiwood_shrublist

local chasms_path = minetest.get_modpath("chasms")

if minetest.get_modpath("df_farming") then
	tower_cap_shrublist = {
		df_farming.spawn_plump_helmet_vm,
		df_farming.spawn_plump_helmet_vm,
		df_farming.spawn_dead_fungus_vm,
		df_farming.spawn_cavern_fungi_vm,
	}
	fungiwood_shrublist = {
		df_farming.spawn_plump_helmet_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 log_location
if mapgen_helper.log_location_enabled then
	log_location = mapgen_helper.log_first_location
end

local subsea_level = df_caverns.config.level1_min - (df_caverns.config.level1_min - df_caverns.config.ymax) * 0.33
local flooding_threshold = math.min(df_caverns.config.tunnel_flooding_threshold, df_caverns.config.cavern_threshold)

local get_biome = function(heat, humidity)
	if humidity < 23 then -- about 20% of locations fall below this threshold
		return "barren"
	elseif heat < 50 then
		return "towercap"
	else
		return "fungiwood"
	end
end

df_caverns.register_biome_check(function(pos, heat, humidity)
	if pos.y < df_caverns.config.level1_min or pos.y > df_caverns.config.ymax then
		return nil
	end
	return get_biome(heat, humidity)
end)

local tower_cap_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, tower_cap_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.04 then
				df_trees.spawn_spindlestem_vm(vi+ystride, area, data, data_param2, c_spindlestem_white)
			end
		end
	end
end

local fungiwood_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, fungiwood_shrublist)
		elseif math.random() < 0.03 and abs_cracks > 0.35 then
			df_trees.spawn_fungiwood_vm(vi+ystride, area, data)
		elseif math.random() < 0.04 then
			df_trees.spawn_spindlestem_vm(vi+ystride, area, data, data_param2)
		end
	end
end

local decorate_level_1 = 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 humiditymap = minetest.get_mapgen_object("humiditymap")
	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 nvals_cave = node_arrays.nvals_cave
	local cave_area = node_arrays.cave_area

	-- Partly fill flooded caverns and warrens
	if minp.y <= subsea_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 <= subsea_level and data[vi] == c_air and nvals_cave[vi] < -flooding_threshold then
				data[vi] = c_water
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.cavern_floor_nodes) do
		local vert_rand = mapgen_helper.xz_consistent_randomi(area, vi)
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local abs_cracks = math.abs(nvals_cracks[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if minp.y < subsea_level and area:get_y(vi) < subsea_level and flooded_caverns then
			-- underwater floor
			df_caverns.flooded_cavern_floor(abs_cracks, vert_rand, vi, area, data)
			if log_location then log_location("level1_flooded_"..biome_name, area:position(vi)) end
		elseif biome_name == "towercap" then
			tower_cap_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
			if log_location then log_location("level1_towercap", area:position(vi)) end
		elseif biome_name == "fungiwood"  then
			fungiwood_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
			if log_location then log_location("level1_fungiwood", area:position(vi)) end
		elseif biome_name == "barren" then
			if flooded_caverns then
				df_caverns.wet_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
				if log_location then log_location("level1_barren_wet", area:position(vi)) end
			else
				df_caverns.dry_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
				if log_location then log_location("level1_barren_dry", area:position(vi)) end
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.cavern_ceiling_nodes) do
		local vert_rand = mapgen_helper.xz_consistent_randomi(area, vi)
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local abs_cracks = math.abs(nvals_cracks[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level then
			-- underwater ceiling, do nothing
		elseif biome_name == "barren" then
			if flooded_caverns then
				-- wet barren
				if abs_cracks < 0.1 then
					df_caverns.stalactites(abs_cracks, vert_rand, vi, area, data, data_param2, true)
				end
			else
				-- dry barren
				if abs_cracks < 0.075 then
					df_caverns.stalactites(abs_cracks, vert_rand, vi, area, data, data_param2, false)
				end
			end
		else -- all the other biomes
			df_caverns.glow_worm_cavern_ceiling(abs_cracks, vert_rand, vi, area, data, data_param2)
		end
	end

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

	for _, vi in ipairs(node_arrays.tunnel_floor_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
			if flooded_caverns or biome_name ~= "barren" then
				-- we're in flooded areas or are not barren
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, false, c_gravel)
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.tunnel_ceiling_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
			if flooded_caverns or biome_name ~= "barren" then
				-- we're in flooded areas or are not barren
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, false)
			end
		else
			-- air pockets
			local ystride = area.ystride
			local cracks = nvals_cracks[index2d]
			if cracks > 0.5 and data[vi-ystride] == c_water then
				data[vi-ystride] = c_air
				if cracks > 0.7 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
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.
		local ystride = area.ystride

		if log_location then
			local flood_name = ""
			if flooded_caverns then flood_name = "_flooded" end
			log_location("level1_warren_"..biome_name..flood_name, area:position(vi))
		end

		if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
			if flooded_caverns or biome_name ~= "barren" then
				-- we're in flooded areas or are not barren
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, false, c_gravel)
			end

			if biome_name ~= "barren" then
				local cracks = nvals_cracks[index2d]
				if cracks > 0.25 then
					local rand = math.random()
					if rand > cracks then
						if math.random() < 0.25 then
							data[vi] = c_dirt_moss
						else
							data[vi] = c_dirt
						end
						if data[vi+ystride] == c_air and math.random() < 0.25 then
							df_caverns.place_shrub(vi+ystride, area, data, data_param2, tower_cap_shrublist)
						end
					end
					if rand > cracks*2 then
						df_trees.spawn_spindlestem_vm(vi+ystride, area, data, data_param2)
					end
				end
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.warren_ceiling_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
			if flooded_caverns or biome_name ~= "barren" then
				-- we're in flooded areas or are not barren
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, false)
			end
		end
		-- else air pockets?
	end

	----------------------------------------------
	-- Column material override for dry biome

	for _, vi in ipairs(node_arrays.column_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local dry = (biome_name == "barren") and (nvals_cave[vi] > 0)

		if dry and data[vi] == c_wet_flowstone then
			data[vi] = c_dry_flowstone
		end

		if chasms_path then
			local pos = area:position(vi)
			if chasms.is_in_chasm_without_taper(pos) then
				local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.
				if flooded_caverns and pos.y < subsea_level then
					data[vi] = c_water
				else
					data[vi] = c_air
				end
			end
		end
	end

	vm:set_param2_data(data_param2)
end

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

subterrane.register_layer({
	name = "cavern layer 1",
	y_max = df_caverns.config.ymax,
	y_min = df_caverns.config.level1_min,
	cave_threshold = df_caverns.config.cavern_threshold,
	boundary_blend_range = 64, -- range near ymin and ymax over which caves diminish to nothing
	perlin_cave = df_caverns.perlin_cave,
	perlin_wave = df_caverns.perlin_wave,
	solidify_lava = true,
	columns = {
		maximum_radius = 10,
		minimum_radius = 4,
		node = "df_mapitems:wet_flowstone",
		weight = 0.25,
		maximum_count = 50,
		minimum_count = 0,
	},
	decorate = decorate_level_1,
	warren_region_variability_threshold = 0.33,
	double_frequency = true,
	is_ground_content = df_caverns.is_ground_content,
})


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_gravel = df_caverns.node_id.gravel

local c_stillworm = df_caverns.node_id.stillworm
local c_pebble_fungus = df_caverns.node_id.pebble_fungus
local c_red = df_caverns.node_id.spindlestem_red

local c_wet_flowstone = df_caverns.node_id.wet_flowstone
local c_dry_flowstone = df_caverns.node_id.dry_flowstone

local c_veinstone = df_caverns.node_id.veinstone
local c_pearls = df_caverns.node_id.pearls

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 wall_vein_perlin_params = {
	offset = 0,
	scale = 1,
	spread = {x = 50, y = 50, z = 50},
	seed = 2199,
	octaves = 3,
	persist = 0.63,
	lacunarity = 2.0,
	flags = "eased",
}

local subsea_level = df_caverns.config.level2_min - (df_caverns.config.level2_min - df_caverns.config.level1_min) * 0.33 -- "sea level" for the flooded caverns.
local flooding_threshold = math.min(df_caverns.config.tunnel_flooding_threshold, df_caverns.config.cavern_threshold) -- cavern value out to which we're flooding tunnels and warrens

local get_biome = function(heat, humidity)
	if humidity < 23 then  -- about 20% of locations fall below this threshold
		return "barren"
	elseif heat < 40 then
		return "goblincap" -- about 33% are below this threshold
	elseif heat < 60 then
		return "sporetree" -- another 33%
	else
		return "tunneltube"
	end
end

df_caverns.register_biome_check(function(pos, heat, humidity)
	if pos.y < df_caverns.config.level2_min or pos.y >= df_caverns.config.level1_min then
		return nil
	end
	return get_biome(heat, humidity)
end)

local goblin_cap_shrublist
local tunnel_tube_shrublist
local spore_tree_shrublist

if minetest.get_modpath("df_farming") then
	goblin_cap_shrublist = {
		df_farming.spawn_plump_helmet_vm,
		df_farming.spawn_plump_helmet_vm,
		df_farming.spawn_dead_fungus_vm,
		df_farming.spawn_cavern_fungi_vm,
	}
	tunnel_tube_shrublist = {
		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,
	}
	spore_tree_shrublist = {
		df_farming.spawn_pig_tail_vm,
		df_farming.spawn_pig_tail_vm,
		df_farming.spawn_cave_wheat_vm,
		df_farming.spawn_dead_fungus_vm,
		df_farming.spawn_cavern_fungi_vm,
	}
end

local goblin_cap_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 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, goblin_cap_shrublist)
		elseif math.random() < 0.02 then
			df_trees.spawn_spindlestem_vm(vi+ystride, area, data, data_param2, c_red)
		elseif math.random() < 0.015 then
			df_trees.spawn_goblin_cap_vm(vi+ystride, area, data, data_param2)
		end
	end

end

local spore_tree_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 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_pebble_fungus
		end
		if math.random() < 0.1 then
			df_caverns.place_shrub(vi+ystride, area, data, data_param2, spore_tree_shrublist)
		elseif math.random() < 0.05 then
			df_trees.spawn_spore_tree_vm(vi+ystride, area, data, data_param2)
		end
	end
end

local tunnel_tube_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 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_stillworm
		end
		if math.random() < 0.1 then
			df_caverns.place_shrub(vi+ystride, area, data, data_param2, tunnel_tube_shrublist)
		elseif math.random() < 0.05 then
			df_trees.spawn_tunnel_tube_vm(vi+ystride, area, data, data_param2)
		end
	end
end

local decorate_level_2 = 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 humiditymap = minetest.get_mapgen_object("humiditymap")
	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 nvals_cave = node_arrays.nvals_cave
	local cave_area = node_arrays.cave_area
	local cavern_def = node_arrays.cavern_def

	local vein_noise
	local vein_area

	-- Partly fill flooded caverns and warrens
	for vi, x, y, z in area:iterp_yxz(area.MinEdge, area.MaxEdge) do
		local cave_val = nvals_cave[vi]
		if cave_val < -flooding_threshold then
			if mapgen_helper.is_pos_within_box({x=x, y=y, z=z}, minp, maxp) then
				local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
				local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
				local cave_threshold = cavern_def.cave_threshold

				--check if we're just inside the boundary of the (negazone) cavern threshold
				if biome_name == "barren" and cave_val < -cave_threshold and cave_val > -cave_threshold - 0.01 then
					-- add giant rooty structures to the flooded barren caverns
					if vein_noise == nil then
						vein_noise, vein_area = mapgen_helper.perlin3d("df_caverns:wall_veins", minp, maxp, wall_vein_perlin_params)
					end
					if data[vi] == c_air and math.abs(vein_noise[vein_area:transform(area, vi)]) < 0.02 then
						data[vi] = c_veinstone
					end
					if log_location then log_location("level2_veinstone", area:position(vi)) end
				end
			end
			if data[vi] == c_air and y <= subsea_level then
				data[vi] = c_water -- otherwise, fill air with water when below sea level
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.cavern_floor_nodes) do
		local vert_rand = mapgen_helper.xz_consistent_randomi(area, vi)
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local abs_cracks = math.abs(nvals_cracks[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if minp.y < subsea_level and area:get_y(vi) < subsea_level and flooded_caverns then
			-- underwater floor
			df_caverns.flooded_cavern_floor(abs_cracks, vert_rand, vi, area, data)
			if log_location then log_location("level2_flooded_"..biome_name, area:position(vi)) end
		elseif biome_name == "barren" then
			if flooded_caverns then
				df_caverns.wet_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
				if log_location then log_location("level2_barren_wet", area:position(vi)) end
			else
				df_caverns.dry_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
				if log_location then log_location("level2_barren_dry", area:position(vi)) end
			end
		elseif biome_name == "goblincap" then
			goblin_cap_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
			if log_location then log_location("level2_goblincap", area:position(vi)) end
		elseif biome_name == "sporetree" then
			spore_tree_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
			if log_location then log_location("level2_sporetree", area:position(vi)) end
		elseif biome_name == "tunneltube" then
			tunnel_tube_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
			if log_location then log_location("level2_tunneltube", area:position(vi)) end
		end
	end

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

	for _, vi in ipairs(node_arrays.cavern_ceiling_nodes) do
		local vert_rand = mapgen_helper.xz_consistent_randomi(area, vi)
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local abs_cracks = math.abs(nvals_cracks[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level then
			-- underwater ceiling, do nothing
		elseif biome_name == "barren" then
			if flooded_caverns then
				-- wet barren
				if abs_cracks < 0.1 then
					df_caverns.stalactites(abs_cracks, vert_rand, vi, area, data, data_param2, true)
				end
			else
				-- dry barren
				if abs_cracks < 0.075 then
					df_caverns.stalactites(abs_cracks, vert_rand, vi, area, data, data_param2, false)
				end
				local y = area:get_y(vi)
				local y_proportional = (y - df_caverns.config.level1_min) / (df_caverns.config.level2_min - df_caverns.config.level1_min)
				if abs_cracks * y_proportional > 0.3 and math.random() < 0.005 * y_proportional then
					df_mapitems.place_big_crystal_cluster(area, data, data_param2, vi, math.random(0,1), true)
				end
			end
		else -- all the other biomes
			df_caverns.glow_worm_cavern_ceiling(abs_cracks, vert_rand, vi, area, data, data_param2)
		end
	end

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

	for _, vi in ipairs(node_arrays.tunnel_floor_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
			if flooded_caverns or biome_name ~= "barren" then
				-- we're in flooded areas or are not barren
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, false, c_gravel)
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.tunnel_ceiling_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.
		local ystride = area.ystride

		if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
			if flooded_caverns or biome_name ~= "barren" then
				-- we're in flooded areas or are not barren
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, false)
			end
			if not flooded_caverns and (biome_name == "barren" or biome_name == "sporetree") and nvals_cracks[index2d] > 0.5 then
				for i= 1, 4 do
					if math.random() > 0.5 then
						local index = vi-i*ystride
						if data[index] == c_air then
							df_mapitems.place_against_surface_vm(c_pearls, index, area, data, data_param2)
						end
					end
				end
			end
		else
			-- air pockets
			local ystride = area.ystride
			local cracks = nvals_cracks[index2d]
			if cracks > 0.4 and data[vi-ystride] == c_water then
				data[vi-ystride] = c_air
				if cracks > 0.6 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
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if log_location then
			local flood_name = ""
			if flooded_caverns then flood_name = "_flooded" end
			log_location("level2_warren_"..biome_name..flood_name, area:position(vi))
		end

		if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
			if flooded_caverns or biome_name ~= "barren" then
				-- we're in flooded areas or are not barren
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, false, c_gravel)
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.warren_ceiling_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.
		local ystride = area.ystride

		if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
			if flooded_caverns or biome_name ~= "barren" then
				-- we're in flooded areas or are not barren
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, false)
			end

			if not flooded_caverns and (biome_name == "barren" or biome_name == "sporetree") and nvals_cracks[index2d] > 0.5 then
				for i= 1, 4 do
					if math.random() > 0.5 then
						local index = vi-i*ystride
						if data[index] == c_air then
							df_mapitems.place_against_surface_vm(c_pearls, index, area, data, data_param2)
						end
					end
				end
			end
		else
			-- air pockets
			local cracks = nvals_cracks[index2d]
			if cracks > 0.4 and data[vi-ystride] == c_water then
				data[vi-ystride] = c_air
				if cracks > 0.6 and data[vi-ystride*2] == c_water then
					data[vi-ystride*2] = c_air
				end
			end
		end

	end

	----------------------------------------------
	-- Column material override for dry biome

	for _, vi in ipairs(node_arrays.column_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local dry = (biome_name == "barren") and (nvals_cave[vi] > 0)

		if dry and data[vi] == c_wet_flowstone then
			data[vi] = c_dry_flowstone
		end

		if chasms_path then
			local pos = area:position(vi)
			if chasms.is_in_chasm_without_taper(pos) then
				local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.
				if flooded_caverns and pos.y < subsea_level then
					data[vi] = c_water
				else
					data[vi] = c_air
				end
			end
		end

	end

	vm:set_param2_data(data_param2)
end

subterrane.register_layer({
	name = "cavern layer 2",
	y_max = df_caverns.config.level1_min-1,
	y_min = df_caverns.config.level2_min,
	cave_threshold = df_caverns.config.cavern_threshold,
	boundary_blend_range = 64, -- range near ymin and ymax over which caves diminish to nothing
	perlin_cave = df_caverns.perlin_cave,
	perlin_wave = df_caverns.perlin_wave,
	solidify_lava = true,
	columns = {
		maximum_radius = 15,
		minimum_radius = 4,
		node = "df_mapitems:wet_flowstone",
		weight = 0.25,
		maximum_count = 50,
		minimum_count = 5,
	},
	decorate = decorate_level_2,
	warren_region_variability_threshold = 0.33,
	double_frequency = true,
	is_ground_content = df_caverns.is_ground_content,
})


code:
local c_water = df_caverns.node_id.water
local c_air = df_caverns.node_id.air
local c_desert_sand = df_caverns.node_id.desert_sand
local c_stone_with_coal = df_caverns.node_id.stone_with_coal
local c_silver_sand = df_caverns.node_id.silver_sand
local c_snow = df_caverns.node_id.snow
local c_ice = df_caverns.node_id.ice
local c_hoar_moss = df_caverns.node_id.hoar_moss
local c_gravel = df_caverns.node_id.gravel
local c_oil = df_caverns.node_id.oil
local c_cobble_fungus_fine = df_caverns.node_id.cobble_fungus_fine
local c_cobble_fungus = df_caverns.node_id.cobble_fungus
local c_cobble = df_caverns.node_id.cobble
local c_wet_flowstone = df_caverns.node_id.wet_flowstone
local c_dry_flowstone = df_caverns.node_id.dry_flowstone
local c_glow_ore = df_caverns.node_id.glow_ore
local c_salty_cobble = df_caverns.node_id.salty_cobble
local c_salt_crystal = df_caverns.node_id.salt_crystal
local c_sprite = df_caverns.node_id.sprite
local c_webs_egg = df_caverns.node_id.big_webs_egg

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 subsea_level = math.floor(df_caverns.config.level3_min - (df_caverns.config.level3_min - df_caverns.config.level2_min) * 0.33)
local flooding_threshold = math.min(df_caverns.config.tunnel_flooding_threshold, df_caverns.config.cavern_threshold)

local ice_thickness = 3

local get_biome = function(heat, humidity)
	if humidity < 23 then -- about 20% of locations fall below this threshold
		return "barren"
	elseif heat < 50 then
		return "blackcap"
	else
		return "bloodnether"
	end
end

df_caverns.register_biome_check(function(pos, heat, humidity)
	if pos.y < df_caverns.config.level3_min or pos.y >= df_caverns.config.level2_min then
		return nil
	end
	local biome = get_biome(heat, humidity)
	if biome == "bloodnether" then
		if subterrane.get_cavern_value("cavern layer 3", pos) < 0 then
			return "nethercap"
		end
		return "bloodthorn"
	end
	return biome
end)

local black_cap_shrublist
local nether_cap_shrublist
local blood_thorn_shrublist

if minetest.get_modpath("df_farming") then
	black_cap_shrublist = {
		df_farming.spawn_dead_fungus_vm,
	}
	nether_cap_shrublist = {
		df_farming.spawn_dead_fungus_vm,
		df_farming.spawn_dead_fungus_vm,
		df_farming.spawn_dead_fungus_vm,
		df_farming.spawn_cavern_fungi_vm,
	}
	blood_thorn_shrublist = {
		df_farming.spawn_quarry_bush_vm,
		df_farming.spawn_dead_fungus_vm,
		df_farming.spawn_dead_fungus_vm,
	}
end

local hoar_moss_perlin_params = {
	offset = 0,
	scale = 1,
	spread = {x = 3, y = 30, z = 3},
	seed = 345421,
	octaves = 3,
	persist = 0.63,
	lacunarity = 2.0,
	flags = "eased",
}

local black_cap_cavern_floor = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	if math.random() < 0.25 then
		data[vi] = c_stone_with_coal
	else
		data[vi] = c_cobble_fungus
	end

	if abs_cracks < 0.1 then
		df_caverns.stalagmites(abs_cracks, vert_rand, vi, area, data, data_param2, false)
	elseif abs_cracks < 0.15 and math.random() < 0.3 then
		df_trees.spawn_torchspine_vm(vi+area.ystride, area, data, data_param2)
	else
		if math.random() < 0.05 then
			df_caverns.place_shrub(vi+area.ystride, area, data, data_param2, black_cap_shrublist)
		elseif math.random() < 0.01 and abs_cracks > 0.25 then
			df_trees.spawn_black_cap_vm(vi+area.ystride, area, data)
		end
	end
end

local nether_cap_cavern_floor = function(cracks, abs_cracks, vert_rand, vi, area, data, data_param2)
	local ystride = area.ystride
	if abs_cracks < 0.1 then
		if vert_rand < 0.004 then
			subterrane.big_stalagmite(vi+ystride, area, data, 6, 15, c_ice, c_ice, c_ice)
		else
			local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
			local height = abs_cracks * 50
			if vert_rand > 0.5 then
				subterrane.stalagmite(vi+ystride, area, data, data_param2, param2, math.floor(height), df_mapitems.icicle_ids)
			else
				subterrane.stalagmite(vi+ystride, area, data, data_param2, param2, math.floor(height*0.5), df_mapitems.dry_stalagmite_ids)
			end
		end
	end

	if cracks < -0.3 then
		data[vi] = c_silver_sand
		if  math.random() < 0.025 then
			df_trees.spawn_nether_cap_vm(vi+ystride, area, data)
		elseif math.random() < 0.05 then
			df_caverns.place_shrub(vi+ystride, area, data, data_param2, nether_cap_shrublist)
		elseif cracks < -0.4 and cracks > -0.6 then
			data[vi + ystride] = c_snow
		end
	elseif cracks > 0.1 then
		if math.random() < 0.002 then
			df_trees.spawn_nether_cap_vm(vi+ystride, area, data)
		else
			data[vi] = c_ice
		end
		if cracks > 0.4 then
			data[vi + ystride] = c_ice
			if cracks > 0.6 then
				data[vi + 2*ystride] = c_ice
			end
		end
	end
end

local nether_cap_cavern_ceiling = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	local ystride = area.ystride
	if abs_cracks < 0.1 then
		if vert_rand < 0.01 then
			subterrane.big_stalactite(vi-ystride, area, data, 6, 15, c_ice, c_ice, c_ice)
		else
			local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
			local height = abs_cracks * 50
			if vert_rand > 0.5 then
				subterrane.stalactite(vi-ystride, area, data, data_param2, param2, math.floor(height), df_mapitems.icicle_ids)
			else
				subterrane.stalactite(vi-ystride, area, data, data_param2, param2, math.floor(height*0.5), df_mapitems.dry_stalagmite_ids)
			end
		end
	end
	if c_sprite and abs_cracks < 0.5 and math.random() < 0.02 then
		local sprite_vi = vi-ystride*math.random(1,5)
		if data[sprite_vi] == c_air and area:containsi(sprite_vi) then
			data[sprite_vi] = c_sprite
			minetest.get_node_timer(area:position(sprite_vi)):start(1)
		end
	end
end

local blood_thorn_cavern_floor = function(abs_cracks, vert_rand, vi, area, data, data_param2)
	local ai = vi+area.ystride

	if abs_cracks < 0.075 then
		if vert_rand < 0.004 then
			subterrane.big_stalagmite(ai, area, data, 6, 15, c_dry_flowstone, c_dry_flowstone, c_dry_flowstone)
		elseif data[vi] ~= c_air and math.random() < 0.5 then
			data[vi] = c_salty_cobble
			if data[ai] == c_air and math.random() < 0.25 then
				data[ai] = c_salt_crystal
				data_param2[ai] = math.random(1,4)-1
			end
		else
			local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
			local height = math.floor(abs_cracks * 66)
			subterrane.stalagmite(ai, area, data, data_param2, param2, height, df_mapitems.dry_stalagmite_ids)
		end
	elseif math.random() > abs_cracks + 0.66 then
		df_trees.spawn_blood_thorn_vm(ai, area, data, data_param2)
		data[vi] = c_desert_sand
	else
		if math.random() < 0.1 then
			df_caverns.place_shrub(ai, area, data, data_param2, blood_thorn_shrublist)
			data[vi] = c_desert_sand
		elseif math.random() > 0.25 then
			data[vi] = c_desert_sand
		else
			data[vi] = c_cobble
		end
	end
end

local hoar_moss_generator

local decorate_level_3 = 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 humiditymap = minetest.get_mapgen_object("humiditymap")
	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 nvals_cave = node_arrays.nvals_cave
	local cave_area = node_arrays.cave_area
	local cavern_def = node_arrays.cavern_def

	-- Partly fill flooded caverns and warrens
	if minp.y <= subsea_level then
		for vi, x, y, z in area:iterp_yxz(area.MinEdge, area.MaxEdge) do
			local cave = nvals_cave[vi]
			if y <= subsea_level and cave < -flooding_threshold then
				if data[vi] == c_air and y <= subsea_level then
					data[vi] = c_water
				end

				if (mapgen_helper.is_pos_within_box({x=x, y=y, z=z}, minp, maxp)) then
					local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
					local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
					if biome_name == "blackcap" then
						-- oil slick
						if y == subsea_level and data[vi] == c_water and math.abs(cave) + nvals_cracks[index2d]*0.025 < cavern_def.cave_threshold + 0.1 then
							data[vi] = c_oil
							if log_location then log_location("level3_blackcap_oil", vector.new(x,y,z)) end
						end
					elseif biome_name == "bloodnether" and y <= subsea_level and y > subsea_level - ice_thickness and data[vi] == c_water then
						-- floating ice
						data[vi] = c_ice
						if log_location then log_location("level3_nethercap_floating_ice", vector.new(x,y,z)) end
					end
				end
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.cavern_floor_nodes) do
		local vert_rand = mapgen_helper.xz_consistent_randomi(area, vi)
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local cracks = nvals_cracks[index2d]
		local abs_cracks = math.abs(cracks)
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level then
			-- underwater floor. Not using df_caverns.flooded_cavern_floor to make level 3 water darker
			local ystride = area.ystride
			if abs_cracks > 0.25 and data[vi-ystride] ~= c_water then
				data[vi] = c_gravel
			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
			if log_location then log_location("level3_flooded_"..biome_name, area:position(vi)) end
		elseif biome_name == "barren" then
			if flooded_caverns then
				-- wet zone floor
				df_caverns.dry_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
				if log_location then log_location("level3_barren_dry", area:position(vi)) end
			else
				-- dry zone floor, add crystals
				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
				if log_location then log_location("level3_barren_wet", area:position(vi)) end
			end
		elseif biome_name == "blackcap" then
			black_cap_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
			if log_location then log_location("level3_blackcap", area:position(vi)) end
		elseif biome_name == "bloodnether" then
			if flooded_caverns then
				nether_cap_cavern_floor(cracks, abs_cracks, vert_rand, vi, area, data, data_param2)
				if log_location then log_location("level3_nethercap", area:position(vi)) end
			else
				blood_thorn_cavern_floor(abs_cracks, vert_rand, vi, area, data, data_param2)
				if log_location then log_location("level3_bloodthorn", area:position(vi)) end
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.cavern_ceiling_nodes) do
		local vert_rand = mapgen_helper.xz_consistent_randomi(area, vi)
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local abs_cracks = math.abs(nvals_cracks[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level then
			-- underwater ceiling, do nothing

		elseif biome_name == "blackcap" then
			if abs_cracks < 0.1 then
				df_caverns.stalactites(abs_cracks, vert_rand, vi, area, data, data_param2, false)
			end
			if math.random() < 0.25 then
				data[vi] = c_stone_with_coal
			end

		elseif biome_name == "barren" then
			if flooded_caverns then
				-- wet zone ceiling
				if abs_cracks < 0.1 then
					df_caverns.stalactites(abs_cracks, vert_rand, vi, area, data, data_param2, true)
				end
			else
				-- 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

		elseif biome_name == "bloodnether" then
			if flooded_caverns then
				--Nethercap ceiling
				nether_cap_cavern_ceiling(abs_cracks, vert_rand, vi, area, data, data_param2)
			else
				-- bloodthorn ceiling
				if abs_cracks < 0.075 then
					if data[vi] ~= c_air and math.random() < 0.5 then
						data[vi] = c_salty_cobble
						local bi = vi - area.ystride
						if data[bi] == c_air and math.random() < 0.25 then
							data[bi] = c_salt_crystal
							data_param2[bi] = 19 + math.random(1,4)
						end
					else
						df_caverns.stalactites(abs_cracks, vert_rand, vi, area, data, data_param2, false)
					end
				elseif abs_cracks > 0.75 and math.random() < 0.05 then
					data[vi] = c_glow_ore
					df_mapitems.place_big_crystal(data, data_param2, vi-area.ystride, true)
				end
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.tunnel_floor_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
			if flooded_caverns or biome_name == "blackcap" then
				-- we're in flooded areas or are not barren
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, false, c_gravel)
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.tunnel_ceiling_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local humidity = humiditymap[index2d]
		local biome_name = get_biome(heatmap[index2d], humidity)
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.
		local ystride = area.ystride

		if not (flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level) then
			if flooded_caverns or biome_name == "blackcap" then
				-- we're in flooded areas or are not barren
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, false)
			end
			if c_webs_egg and humidity < 40 and nvals_cracks[index2d] > 0.5 and math.random() < 0.1 then
				local index = vi-ystride
				if data[index] == c_air then
					data[index] = c_webs_egg
					minetest.get_node_timer(area:position(index)):start(1)
				end
			end
		else
			-- air pockets
			local cracks = nvals_cracks[index2d]
			if cracks > 0.5 and data[vi-ystride] == c_water then
				data[vi-ystride] = c_air
				if cracks > 0.7 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
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if log_location then
			local flood_name = ""
			if flooded_caverns then flood_name = "_flooded" end
			log_location("level3_warren_"..biome_name..flood_name, area:position(vi))
		end

		if flooded_caverns and minp.y < subsea_level and area:get_y(vi) < subsea_level then
			-- underwater ceiling, do nothing
		elseif biome_name == "bloodnether" and flooded_caverns then
			-- Nethercap warrens
			local cracks = nvals_cracks[index2d]
			local abs_cracks = math.abs(cracks)
			local vert_rand = mapgen_helper.xz_consistent_randomi(area, vi)
			local ystride = area.ystride
			if abs_cracks < 0.15 then
				if vert_rand < 0.004 then
					subterrane.big_stalactite(vi-ystride, area, data, 6, 15, c_ice, c_ice, c_ice)
				else
					local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
					local height = abs_cracks * 50
					if vert_rand > 0.5 then
						subterrane.stalactite(vi-ystride, area, data, data_param2, param2, math.floor(height), df_mapitems.icicle_ids)
					else
						subterrane.stalactite(vi-ystride, area, data, data_param2, param2, math.floor(height*0.5), df_mapitems.dry_stalagmite_ids)
					end
				end
			end
		else
			if flooded_caverns or biome_name == "blackcap" then
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_ceiling(minp, maxp, area, vi, nvals_cracks, data, data_param2, false)
			end
		end
	end

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

	for _, vi in ipairs(node_arrays.warren_floor_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0 -- this indicates if we're in the "flooded" set of caves or not.

		if minp.y < subsea_level and area:get_y(vi) < subsea_level and flooded_caverns then
			-- underwater floor, do nothing
		elseif biome_name == "bloodnether" and flooded_caverns then
			-- nethercap warren
			local cracks = nvals_cracks[index2d]
			local abs_cracks = math.abs(cracks)
			local vert_rand = mapgen_helper.xz_consistent_randomi(area, vi)
			local ystride = area.ystride
			if abs_cracks < 0.15 then
				if vert_rand < 0.004 then
					subterrane.big_stalagmite(vi+ystride, area, data, 6, 15, c_ice, c_ice, c_ice)
				else
					local param2 = abs_cracks*1000000 - math.floor(abs_cracks*1000000/4)*4
					local height =abs_cracks * 50
					if vert_rand > 0.5 then
						subterrane.stalagmite(vi+ystride, area, data, data_param2, param2, math.floor(height), df_mapitems.icicle_ids)
					else
						subterrane.stalagmite(vi+ystride, area, data, data_param2, param2, math.floor(height*0.5), df_mapitems.dry_stalagmite_ids)
					end
				end
			elseif cracks > 0.4 then
				data[vi + ystride] = c_ice
				if cracks > 0.6 then
					data[vi + 2*ystride] = c_ice
				end
			end
		else
			if flooded_caverns or biome_name == "blackcap" then
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, true)
			else
				df_caverns.tunnel_floor(minp, maxp, area, vi, nvals_cracks, data, data_param2, false, c_gravel)
			end
		end
	end

	----------------------------------------------
	-- Column material override for dry and icy biomes
	for _, vi in ipairs(node_arrays.column_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local biome_name = get_biome(heatmap[index2d], humiditymap[index2d])
		local flooded_caverns = nvals_cave[vi] < 0

		if biome_name == "bloodnether" and data[vi] == c_wet_flowstone then
			if not flooded_caverns then
				data[vi] = c_dry_flowstone -- bloodthorn
			else
				if area:get_y(vi) > subsea_level - ice_thickness then
					if data[vi + 1] == c_air or data[vi - 1] == c_air or data[vi + area.zstride] == c_air or data[vi - area.zstride] == c_air then
						--surface node, potential hoar moss streak
						-- This particular Perlin noise is only called in small amounts on rare occasions, so don't bother
						-- with the full blown generated array rigamarole.
						hoar_moss_generator = hoar_moss_generator or minetest.get_perlin(hoar_moss_perlin_params)
						local pos = area:position(vi)
						if hoar_moss_generator:get_3d({x=pos.z, y=pos.y, z=pos.x}) > 0.5 then
							data[vi] = c_hoar_moss
						else
							data[vi] = c_ice
						end
					else
						data[vi] = c_ice
					end
				else
					data[vi] = c_water -- ice columns shouldn't extend below the surface of the water. There should probably be a bulge below, though. Not sure best way to implement that.
				end
			end
		elseif biome_name == "barren" and not flooded_caverns and data[vi] == c_wet_flowstone then
			data[vi] = c_dry_flowstone
		end

		if chasms_path then
			local pos = area:position(vi)
			if chasms.is_in_chasm_without_taper(pos) then
				if flooded_caverns and pos.y < subsea_level then
					data[vi] = c_water -- this puts a crack in the ice of icy biomes, but why not? A crack in the ice is interesting.
				else
					data[vi] = c_air
				end
			end
		end

	end

	vm:set_param2_data(data_param2)
end

-- Layer 3
subterrane.register_layer({
	name = "cavern layer 3",
	y_max = df_caverns.config.level2_min-1,
	y_min = df_caverns.config.level3_min,
	cave_threshold = df_caverns.config.cavern_threshold,
	boundary_blend_range = 64, -- range near ymin and ymax over which caves diminish to nothing
	perlin_cave = df_caverns.perlin_cave,
	perlin_wave = df_caverns.perlin_wave,
	solidify_lava = true,
	columns = {
		maximum_radius = 20,
		minimum_radius = 5,
		node = "df_mapitems:wet_flowstone",
		weight = 0.25,
		maximum_count = 50,
		minimum_count = 10,
	},
	decorate = decorate_level_3,
	warren_region_variability_threshold = 0.33,
	double_frequency = true,
	is_ground_content = df_caverns.is_ground_content,
})


code:
# textdomain: df_caverns

### underworld.lua ###

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
A glowing pit=Eine leuchtende Grube
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Ancient ruin=Alte Ruine
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Mysterious seal=Geheimnisvolles Siegel


code:
# textdomain: df_caverns

### underworld.lua ###

#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
A glowing pit=Un pozzo luminoso
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Ancient ruin=Antica rovina
#WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE
Mysterious seal=Sigillo misterioso


code:
# textdomain: df_caverns

### underworld.lua ###

A glowing pit=
Ancient ruin=
Mysterious seal=


code:
name = df_caverns
description = Adds vast underground caverns in the style of Dwarf Fortress, complete with underground flora in diverse biomes. Also adds stalactite/stalagmite decorations in the smaller tunnels.
depends = df_dependencies, subterrane, df_trees, df_mapitems
optional_depends = df_farming, ice_sprites, oil, df_underworld_items, magma_conduits, bones_loot, named_waypoints, name_generator, fireflies, chasms, big_webs, mcl_flowers


code:
df_caverns.node_id = {}

minetest.after(0, function() df_caverns.node_id = nil end) -- should only be used during initialization.

if df_dependencies.node_name_fireflies then
	df_caverns.node_id.fireflies = minetest.get_content_id(df_dependencies.node_name_fireflies)
end

df_caverns.node_id.cobble = minetest.get_content_id(df_dependencies.node_name_cobble)
df_caverns.node_id.desert_sand = minetest.get_content_id(df_dependencies.node_name_desert_sand)
df_caverns.node_id.dirt = minetest.get_content_id(df_dependencies.node_name_dirt)
df_caverns.node_id.gravel = minetest.get_content_id(df_dependencies.node_name_gravel)
df_caverns.node_id.ice = minetest.get_content_id(df_dependencies.node_name_ice)
df_caverns.node_id.lava = minetest.get_content_id(df_dependencies.node_name_lava_source)
df_caverns.node_id.meseore = minetest.get_content_id(df_dependencies.node_name_stone_with_mese)
df_caverns.node_id.mossycobble = minetest.get_content_id(df_dependencies.node_name_mossycobble)
df_caverns.node_id.obsidian = minetest.get_content_id(df_dependencies.node_name_obsidian)
df_caverns.node_id.sand = minetest.get_content_id(df_dependencies.node_name_sand)
df_caverns.node_id.silver_sand = minetest.get_content_id(df_dependencies.node_name_silver_sand)
df_caverns.node_id.snow = minetest.get_content_id(df_dependencies.node_name_snow)
df_caverns.node_id.stone = minetest.get_content_id(df_dependencies.node_name_stone)
df_caverns.node_id.stone_with_coal = minetest.get_content_id(df_dependencies.node_name_stone_with_coal)
df_caverns.node_id.water = minetest.get_content_id(df_dependencies.node_name_water_source)

if minetest.get_modpath("big_webs") then
	df_caverns.node_id.big_webs = minetest.get_content_id("big_webs:webbing")
	df_caverns.node_id.big_webs_egg = minetest.get_content_id("big_webs:web_egg")
end

if minetest.get_modpath("ice_sprites") then
	df_caverns.node_id.sprite = minetest.get_content_id("ice_sprites:ice_sprite")
end

if minetest.get_modpath("df_farming") then
	df_caverns.node_id.dead_fungus = minetest.get_content_id("df_farming:dead_fungus")
end

df_caverns.node_id.air = minetest.get_content_id("air")

df_caverns.node_id.cobble_fungus = minetest.get_content_id("df_mapitems:cobble_with_floor_fungus")
df_caverns.node_id.cobble_fungus_fine = minetest.get_content_id("df_mapitems:cobble_with_floor_fungus_fine")
df_caverns.node_id.dirt_moss = minetest.get_content_id("df_mapitems:dirt_with_cave_moss")
df_caverns.node_id.dry_flowstone = minetest.get_content_id("df_mapitems:dry_flowstone")
df_caverns.node_id.glow_ore = minetest.get_content_id("df_mapitems:glow_ruby_ore")
df_caverns.node_id.hoar_moss = minetest.get_content_id("df_mapitems:ice_with_hoar_moss")
df_caverns.node_id.mese_crystal = minetest.get_content_id("df_mapitems:mese_crystal")
df_caverns.node_id.mese_crystal_block = minetest.get_content_id("df_mapitems:glow_mese")
df_caverns.node_id.pearls = minetest.get_content_id("df_mapitems:cave_pearls")
df_caverns.node_id.pebble_fungus = minetest.get_content_id("df_mapitems:dirt_with_pebble_fungus")
df_caverns.node_id.rock_rot = minetest.get_content_id("df_mapitems:rock_rot")
df_caverns.node_id.spongestone = minetest.get_content_id("df_mapitems:spongestone")
df_caverns.node_id.salt_crystal = minetest.get_content_id("df_mapitems:salt_crystal")
df_caverns.node_id.salty_cobble = minetest.get_content_id("df_mapitems:salty_cobble")
df_caverns.node_id.sand_scum = minetest.get_content_id("df_mapitems:sand_scum")
df_caverns.node_id.stillworm = minetest.get_content_id("df_mapitems:dirt_with_stillworm")
df_caverns.node_id.veinstone = minetest.get_content_id("df_mapitems:veinstone")
df_caverns.node_id.wet_flowstone = minetest.get_content_id("df_mapitems:wet_flowstone")

df_caverns.node_id.spindlestem_red = minetest.get_content_id("df_trees:spindlestem_cap_red")
df_caverns.node_id.spindlestem_white = minetest.get_content_id("df_trees:spindlestem_cap_white")

df_caverns.node_id.amethyst = minetest.get_content_id("df_underworld_items:glow_amethyst")
df_caverns.node_id.glowstone = minetest.get_content_id("df_underworld_items:glowstone")
df_caverns.node_id.pit_plasma = minetest.get_content_id("df_underworld_items:pit_plasma")
df_caverns.node_id.slade = minetest.get_content_id("df_underworld_items:slade")
df_caverns.node_id.slade_block = minetest.get_content_id("df_underworld_items:slade_block")

df_caverns.node_id.oil = minetest.get_content_id("oil:oil_source")

df_caverns.node_id.gas = minetest.get_content_id("mine_gas:gas")
df_caverns.node_id.gas_wisp = minetest.get_content_id("mine_gas:gas_wisp")

df_caverns.node_id.giant_mycelium = minetest.get_content_id("df_primordial_items:giant_hypha_apical_mapgen")
df_caverns.node_id.ivy = minetest.get_content_id("df_primordial_items:jungle_ivy")
df_caverns.node_id.jungle_dirt = minetest.get_content_id("df_primordial_items:dirt_with_jungle_grass")
df_caverns.node_id.mycelial_dirt = minetest.get_content_id("df_primordial_items:dirt_with_mycelium")
df_caverns.node_id.orb = minetest.get_content_id("df_primordial_items:glow_orb_hanging")
df_caverns.node_id.packed_roots = minetest.get_content_id("df_primordial_items:packed_roots")
df_caverns.node_id.plant_matter = minetest.get_content_id("df_primordial_items:plant_matter")
df_caverns.node_id.root_1 = minetest.get_content_id("df_primordial_items:jungle_roots_1")
df_caverns.node_id.root_2 = minetest.get_content_id("df_primordial_items:jungle_roots_2")


code:
if not df_caverns.config.enable_oil_sea then
	return
end

local c_oil = df_caverns.node_id.oil
local c_gas = df_caverns.node_id.gas
local c_gas_wisp = df_caverns.node_id.gas_wisp
local c_lava = df_caverns.node_id.lava
local c_obsidian = df_caverns.node_id.obsidian

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

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

local perlin_cave = {
	offset = 0,
	scale = 1,
	spread = {x=300, y=300, z=300},
	seed = 6000089,
	octaves = 3,
	persist = 0.67
}

-- large-scale rise and fall
local perlin_wave = {
	offset = 0,
	scale = 1,
	spread = {x=1000, y=1000, z=1000},
	seed = 10089,
	octaves = 3,
	persist = 0.67
}

local median = df_caverns.config.oil_sea_level
local floor_mult = -80
local floor_displace = 60
local ceiling_mult = 40
local ceiling_displace = -30
local wave_mult = 10

local c_lava_set

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

df_caverns.register_biome_check(function(pos, heat, humidity)
	if pos.y > y_max or pos.y < y_min then
		return
	end
	return "oil_sea"
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, area = mapgen_helper.mapgen_vm_data()

	local nvals_cave = mapgen_helper.perlin2d("df_caverns:oil_cave", minp, maxp, perlin_cave)
	local nvals_wave = mapgen_helper.perlin2d("df_caverns:oil_wave", minp, maxp, perlin_wave)

	if c_lava_set == nil then
		c_lava_set = {}
		for name, def in pairs(minetest.registered_nodes) do
			if def.groups ~= nil and def.groups.lava ~= nil then
				c_lava_set[minetest.get_content_id(name)] = true
			end
		end
	end

	for vi, x, y, z in area:iterp_yxz(minp, maxp) do
		local index2d = mapgen_helper.index2d(minp, maxp, 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 + floor_displace + median + wave)
		local ceiling_height = math.floor(abs_cave * ceiling_mult + median + ceiling_displace + wave)

		if y > floor_height - 5 and y < ceiling_height + 5 then
			if c_lava_set[data[vi]] then
				data[vi] = c_obsidian
			end
		end
		if y > floor_height and y < ceiling_height then
			if y > median then
				if math.random() < 0.00025 and ((y < median + 3) or (y > ceiling_height - 3)) then
					data[vi] = c_gas_wisp
				else
					data[vi] = c_gas
				end
			else
				data[vi] = c_oil
				if log_location then log_location("oil_sea", area:position(vi)) end
			end
		end

	end

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

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

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

minetest.register_ore({
	ore_type       = "scatter",
	ore            = "mine_gas:gas_seep",
	wherein        = df_dependencies.node_name_stone,
	clust_scarcity = 32 * 32 * 32,
	clust_num_ores = 27,
	clust_size     = 6,
	y_max          = df_caverns.config.sunless_sea_min,
	y_min          = y_min,
})


code:
if not df_caverns.config.enable_primordial or not minetest.get_modpath("df_primordial_items") then
	return
end

local c_air = df_caverns.node_id.air

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

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

local perlin_wave_primordial = {
	offset = 0,
	scale = 1,
	spread = {x=df_caverns.config.horizontal_cavern_scale, y=df_caverns.config.vertical_cavern_scale*0.5, z=df_caverns.config.horizontal_cavern_scale},
	seed = 923444,
	octaves = 6,
	persist = 0.63
}

local giant_mycelium_timer_spread = tonumber(minetest.settings:get("dcaverns_giant_mycelium_timer_spread")) or 10

df_caverns.register_biome_check(function(pos, heat, humidity)
	if pos.y < df_caverns.config.primordial_min or pos.y > df_caverns.config.primordial_max then
		return nil
	end
	if subterrane.get_cavern_value("primordial", pos) < 0 then
		return "primordial jungle"
	end
	return "primordial fungus"
end)

-----------------------------------------------------------------------------------------
-- Fungal biome

local c_orb = df_caverns.node_id.orb
local c_mycelial_dirt = df_caverns.node_id.mycelial_dirt
local c_dirt = df_caverns.node_id.dirt
local c_giant_mycelium = df_caverns.node_id.giant_mycelium

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

local mushroom_cavern_floor = function(abs_cracks, humidity, vi, area, data, data_param2)
	local ystride = area.ystride
	local humidityfactor = humidity/200 + 0.5
	abs_cracks = abs_cracks * humidityfactor

	if abs_cracks < 0.7 then
		data[vi] = c_mycelial_dirt
	elseif abs_cracks < 1 then
		data[vi] = c_dirt
	end

	local rand = math.random() * math.min(abs_cracks, 1) * humidityfactor
	if rand < 0.0005 then
		local mycelium_index = vi+ystride
		data[mycelium_index] = c_giant_mycelium
		minetest.get_node_timer(area:position(mycelium_index)):start(math.random(1,giant_mycelium_timer_spread))
	elseif rand < 0.003 then
		local schematic = df_primordial_items.get_primordial_mushroom()
		local rotation = (math.random(1,4)-1)*90
		mapgen_helper.place_schematic_on_data_if_it_fits(data, data_param2, area, area:position(vi+ystride), schematic, rotation)
	elseif rand < 0.05 then
		data[vi+ystride] = fungal_plants[math.random(1,5)]
	end
end

local mushroom_cavern_ceiling = function(abs_cracks, humidity, vi, area, data, data_param2)
	local ystride = area.ystride
	local humidityfactor = humidity/200 + 0.5
	abs_cracks = abs_cracks * humidityfactor

	if abs_cracks < 0.5 then
		data[vi] = c_mycelial_dirt
		if abs_cracks < 0.3 then
			local rand = math.random() * humidityfactor
			if rand < 0.002 then
				local mycelium_index = vi-ystride
				data[mycelium_index] = c_giant_mycelium
				minetest.get_node_timer(area:position(mycelium_index)):start(math.random(1,giant_mycelium_timer_spread))
			elseif rand < 0.03 then
				df_primordial_items.spawn_ceiling_spire_vm(vi, area, data)
			elseif rand < 0.2 then
				data[vi-ystride] = c_orb
				data_param2[vi-ystride] = math.random(0,179)
			end
		end
	end
end

local mushroom_warren_ceiling = function(abs_cracks, vi, area, data, data_param2)
	local ystride = area.ystride

	if abs_cracks < 0.3 then
		data[vi] = c_mycelial_dirt
		if abs_cracks < 0.2 then
			local rand = math.random()
			if rand < 0.001 then
				local mycelium_index = vi-ystride
				data[mycelium_index] = c_giant_mycelium
				minetest.get_node_timer(area:position(mycelium_index)):start(math.random(1,giant_mycelium_timer_spread))
			elseif rand < 0.2 then
				data[vi-ystride] = c_orb
				data_param2[vi-ystride] = math.random(0,179)
			end
		end
	end
end

local mushroom_warren_floor = function(abs_cracks, vi, area, data, data_param2)
	local ystride = area.ystride
	if abs_cracks < 0.7 then
		data[vi] = c_mycelial_dirt
	elseif abs_cracks < 1 then
		data[vi] = c_dirt
	end
	local rand = math.random() * math.min(abs_cracks, 1)
	if rand < 0.001 then
		local mycelium_index = vi+ystride
		data[mycelium_index] = c_giant_mycelium
		minetest.get_node_timer(area:position(mycelium_index)):start(math.random(1,giant_mycelium_timer_spread))
	elseif rand < 0.03 then
		data[vi+ystride] = fungal_plants[math.random(1,5)]
	end
end

--------------------------------------------------------------------------------------------------
-- Jungle biome

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

local c_jungle_dirt = df_caverns.node_id.jungle_dirt
local c_plant_matter = df_caverns.node_id.plant_matter
local c_packed_roots = df_caverns.node_id.packed_roots
local c_glowstone = df_caverns.node_id.glowstone
local c_ivy = df_caverns.node_id.ivy
local c_root_2 = df_caverns.node_id.root_2
local c_root_1 = df_caverns.node_id.root_1
local c_fireflies = df_caverns.node_id.fireflies

local jungle_cavern_floor = function(abs_cracks, humidity, vi, area, data, data_param2)
	local ystride = area.ystride
	local humidityfactor = humidity/100

	if log_location then
		local pos = area:position(vi)
		log_location("primordial_jungle", pos)
		if humidityfactor < 0.25 then
			log_location("primordial_jungle_low_humidity", pos)
		elseif humidityfactor > 0.75 then
			log_location("primordial_jungle_high_humidity", pos)
		end
	end

	data[vi] = c_jungle_dirt

	local rand = math.random()
	if rand < 0.025 * humidityfactor then
		local fern_schematic = df_primordial_items.get_fern_schematic()
		local rotation = (math.random(1,4)-1)*90
		mapgen_helper.place_schematic_on_data_if_it_fits(data, data_param2, area, area:position(vi+ystride), fern_schematic, rotation)
	elseif rand < 0.025 * (1-humidityfactor) then
		df_primordial_items.spawn_jungle_mushroom_vm(vi+ystride, area, data)
	elseif rand < 0.05 * (1-humidityfactor) then
		df_primordial_items.spawn_jungle_tree_vm(math.random(8,14), vi+ystride, area, data)
	elseif rand < 0.3 then
		data[vi+ystride] = jungle_plants[math.random(1,#jungle_plants)]
	end

	if c_fireflies and math.random() < 0.01 then
		local firefly_vi = vi + ystride * math.random(1, 5)
		if data[firefly_vi] == c_air then
			data[firefly_vi] = c_fireflies
			minetest.get_node_timer(area:position(firefly_vi)):start(1)
		end
	end
end

local jungle_cavern_ceiling = function(abs_cracks, vi, area, data, data_param2)
	if abs_cracks < 0.25 then
		data[vi] = c_glowstone
	elseif abs_cracks > 0.75 and math.random() < 0.1 then
		local ystride = area.ystride
		data[vi] = c_dirt
		local index = vi - ystride
		local hanging_node
		if math.random() < 0.5 then
			hanging_node = c_ivy
		else
			hanging_node = c_root_2
		end
		for i = 1, math.random(16) do
			if data[index] == c_air then
				data[index] = hanging_node
				index = index - ystride
			else
				break
			end
		end
	end
end

local jungle_warren_ceiling = function(abs_cracks, vi, area, data, data_param2)
	if abs_cracks < 0.1 then
		data[vi] = c_glowstone
	elseif abs_cracks > 0.75 and math.random() < 0.1 then
		local ystride = area.ystride
		data[vi] = c_dirt
		local index = vi - ystride
		local hanging_node
		if math.random() < 0.5 then
			hanging_node = c_root_1
		else
			hanging_node = c_root_2
		end
		for i = 1, math.random(8) do
			if data[index] == c_air then
				data[index] = hanging_node
				index = index - ystride
			else
				break
			end
		end
	end
end

local jungle_warren_floor = function(abs_cracks, vi, area, data, data_param2)
	local ystride = area.ystride
	if abs_cracks < 0.7 then
		data[vi] = c_jungle_dirt
		local rand = math.random() * abs_cracks
		if rand < 0.1 then
			data[vi+ystride] = jungle_plants[math.random(1,#jungle_plants)]
		end
	elseif abs_cracks < 1 then
		data[vi] = c_dirt
	end

	if c_fireflies and math.random() < 0.005 then
		local firefly_vi = vi + ystride * math.random(1, 5)
		if data[firefly_vi] == c_air then
			data[firefly_vi] = c_fireflies
			minetest.get_node_timer(area:position(firefly_vi)):start(1)
		end
	end
end
---------------------------------------------------------------------------------------------------------

local decorate_primordial = 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 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 cave_area = node_arrays.cave_area
	local nvals_cave = node_arrays.nvals_cave

	local humiditymap = minetest.get_mapgen_object("humiditymap")

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

	for _, vi in ipairs(node_arrays.cavern_floor_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local cracks = nvals_cracks[index2d]
		local abs_cracks = math.abs(cracks)
		local humidity = humiditymap[index2d]
		local jungle = nvals_cave[vi] < 0

		if jungle then
			jungle_cavern_floor(abs_cracks, humidity, vi, area, data, data_param2)
		else
			mushroom_cavern_floor(abs_cracks, humidity, vi, area, data, data_param2)
			if log_location then log_location("primordial_mushrooms", area:position(vi)) 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 cracks = nvals_cracks[index2d]
		local abs_cracks = math.abs(cracks)
		local jungle = nvals_cave[vi] < 0
		local humidity = humiditymap[index2d]
		if jungle then
			jungle_cavern_ceiling(abs_cracks, vi, area, data, data_param2)
		else
			mushroom_cavern_ceiling(abs_cracks, humidity, vi, area, data, data_param2)
		end
	end

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

--	for _, vi in ipairs(node_arrays.tunnel_floor_nodes) do
--	end

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

--	for _, vi in ipairs(node_arrays.tunnel_ceiling_nodes) do
--	end

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

	for _, vi in ipairs(node_arrays.warren_ceiling_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local cracks = nvals_cracks[index2d]
		local abs_cracks = math.abs(cracks)
		local jungle = nvals_cave[vi] < 0

		if jungle then
			jungle_warren_ceiling(abs_cracks, vi, area, data, data_param2)
		else
			mushroom_warren_ceiling(abs_cracks, vi, area, data, data_param2)
		end

	end

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

	for _, vi in ipairs(node_arrays.warren_floor_nodes) do
		local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
		local cracks = nvals_cracks[index2d]
		local abs_cracks = math.abs(cracks)
		local jungle = nvals_cave[vi] < 0

		if jungle then
			jungle_warren_floor(abs_cracks, vi, area, data, data_param2)
			if log_location then log_location("primordial_jungle_warren", area:position(vi)) end
		else
			mushroom_warren_floor(abs_cracks, vi, area, data, data_param2)
			if log_location then log_location("primordial_mushroom_warren", area:position(vi)) end
		end
	end

	-- columns
	-- no flowstone below the Sunless Sea, replace with something else
	local random_dir = {1, -1, area.zstride, -area.zstride}
	for _, vi in ipairs(node_arrays.column_nodes) do
		local jungle = nvals_cave[vi] < 0
		if jungle then
			data[vi] = c_plant_matter
			minetest.get_node_timer(area:position(vi)):start(math.random(30, 120))
		else
			data[vi] = c_mycelial_dirt
			if math.random() < 0.05 then
				local rand_vi = vi + random_dir[math.random(1,4)]
				if data[rand_vi] == c_air then
					data[rand_vi] = c_giant_mycelium
					minetest.get_node_timer(area:position(rand_vi)):start(math.random(1,giant_mycelium_timer_spread))
				end
			end
		end
	end

	vm:set_param2_data(data_param2)
end

--Primordial Caverns
subterrane.register_layer({
	name = "primordial",
	y_max = df_caverns.config.primordial_max,
	y_min = df_caverns.config.primordial_min,
	cave_threshold = df_caverns.config.sunless_sea_threshold, -- Make the caves a bit bigger than above
	perlin_cave = perlin_cave_primordial,
	perlin_wave = perlin_wave_primordial,
	solidify_lava = true,
	columns = {
		maximum_radius = 20,
		minimum_radius = 5,
		node = df_dependencies.node_name_stone, -- no flowstone below the Sunless Sea, replace with something else
		weight = 0.5,
		maximum_count = 60,
		minimum_count = 10,
	},
	decorate = decorate_primordial,
	double_frequency = true,
	is_ground_content = df_caverns.is_ground_content,
})

minetest.register_ore({
	ore_type = "vein",
	ore = "df_underworld_items:glowstone",
	wherein = {
		"default:stone",
		"default:stone_with_coal",
		"default:stone_with_iron",
		"default:stone_with_copper",
		"default:stone_with_tin",
		"default:stone_with_gold",
		"default:stone_with_diamond",
		"default:dirt",
		"default:sand",
		"default:desert_sand",
		"default:silver_sand",
		"default:gravel",
	},
	column_height_min = 2,
	column_height_max = 6,
	y_min = df_caverns.config.primordial_min,
	y_max = df_caverns.config.primordial_max,
	noise_threshold = 0.9,
	noise_params = {
		offset = 0,
		scale = 3,
		spread = {x=400, y=400, z=400},
		seed = 25111,
		octaves = 4,
		persist = 0.5,
		flags = "eased",
	},
	random_factor = 0,
})

-- Rather than make plants farmable, have them randomly respawn in jungle soil. You can only get them down there by foraging, not farming.
minetest.register_abm({
	label = "Primordial plant growth",
	nodenames = {"df_primordial_items:dirt_with_jungle_grass"},
	neighbors = {"air"},
	interval = 60.0,
	chance = 50,
	action = function(pos, node, active_object_count, active_object_count_wider)
		if minetest.find_node_near(pos, 2, {"group:primordial_jungle_plant"}) == nil then
			local pos_above = {x=pos.x, y=pos.y+1, z=pos.z}
			local node_above = minetest.get_node(pos_above)
			if node_above.name == "air" then
				minetest.set_node(pos_above, {name = jungle_plant_names[math.random(1,#jungle_plant_names)]})
			end
		end
	end,
})

minetest.register_abm({
	label = "Primordial fungus growth",
	nodenames = {"df_primordial_items:dirt_with_mycelium"},
	neighbors = {"air"},
	interval = 60.0,
	chance = 50,
	action = function(pos, node, active_object_count, active_object_count_wider)
		if minetest.find_node_near(pos, 3, {"group:primordial_fungal_plant"}) == nil then
			local pos_above = {x=pos.x, y=pos.y+1, z=pos.z}
			local node_above = minetest.get_node(pos_above)
			if node_above.name == "air" then
				minetest.set_node(pos_above, {name = fungal_plant_names[math.random(1,#fungal_plant_names)]})
			end
		end
	end,
})

-- an ABM to extinguish fires on the primordial layer. Glowstone next to plant life equals too much fire.
local fire_enabled = minetest.settings:get_bool("enable_fire")
if fire_enabled == nil then
	-- enable_fire setting not specified, check for disable_fire
	local fire_disabled = minetest.settings:get_bool("disable_fire")
	if fire_disabled == nil then
		-- Neither setting specified, check whether singleplayer
		fire_enabled = minetest.is_singleplayer()
	else
		fire_enabled = not fire_disabled
	end
end
if fire_enabled and minetest.get_modpath("fire") then
	local primordial_min = df_caverns.config.primordial_min
	local primordial_max = df_caverns.config.primordial_max
	minetest.register_abm({
		label = "Remove fire in the primordial layer",
		nodenames = {"fire:basic_flame"},
		--neighbors = {"fire:basic_flame"},
		interval = 3,
		chance = 3,
		catch_up = false,
		action = function(pos)
			local pos_y = pos.y
			if pos_y > primordial_min and pos_y < primordial_max then
				minetest.remove_node(pos)
			end
		end
	})
end


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_underworld_items:slade_brick", force_place=true }
local n9 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 3, force_place=true }
local n10 = { name = "df_underworld_items:slade_wall", force_place=true }
local n11 = { name = "df_underworld_items:ancient_lantern_slade_worn", prob=192 } -- 50% chance of being force-placed
local n12 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 2, force_place=true }
local n13 = { name = df_dependencies.node_name_stair_slade_brick, param2 = 2, force_place=true }
local n14 = { name = df_dependencies.node_name_stair_outer_slade_brick, param2 = 3, force_place=true }

return {
	name = "df_caverns:lamppost",
	size = {x = 3, y = 15, 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,
		n5, n5, n5,
		n5, n5, n5,
		n5, n5, n5,
		n5, n5, n5,
		n5, n11, n5,
		n5, n5, n5,
		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, n8, n9,
		n5, n8, n5,
		n5, n8, n5,
		n5, n10, n5,
		n5, n10, n5,
		n11, n10, n11,
		n5, n5, n5,
		n1, n1, n1, n1, n1, n1, n1, n1, n1,
		n1, n1, n1, n1, n1, n1, n1, n1, n1,
		n1, n1, n1, n1, n1, n1, n12, n13, n14,
		n5, n5, n5,
		n5, n5, n5,
		n5, n5, n5,
		n5,	n5, n5,
		n5, n11, n5,
		n5, n5, n5,
	}
}