code:
-- More Ores
-- Original mod by Calinou with help from Nore
-- Re-coded by TenPlus1

local S
if minetest.get_modpath("intllib") then
	S = intllib.Getter()
else
	S = function(s) return s end
end

-- Minerals

minetest.register_node("moreores:mineral_silver", {
	description = S("%s Ore"):format(S("Silver")),
	tiles = {"default_stone.png^moreores_mineral_silver.png"},
	groups = {cracky = 3},
	sounds = default.node_sound_stone_defaults(),
	drop = "moreores:silver_lump",
})

minetest.register_node("moreores:mineral_mithril", {
	description = S("%s Ore"):format(S("Mithril")),
	tiles = {"default_stone.png^moreores_mineral_mithril.png"},
	groups = {cracky = 3},
	sounds = default.node_sound_stone_defaults(),
	drop = "moreores:mithril_lump",
})

-- Ores

minetest.register_craftitem("moreores:silver_lump", {
	description = S("%s Lump"):format(S("Silver")),
	inventory_image = "moreores_silver_lump.png",
})

minetest.register_craftitem("moreores:mithril_lump", {
	description = S("%s Lump"):format(S("Mithril")),
	inventory_image = "moreores_mithril_lump.png",
})

-- Ingots

minetest.register_craftitem("moreores:silver_ingot", {
	description = S("%s Ingot"):format(S("Silver")),
	inventory_image = "moreores_silver_ingot.png",
})

minetest.register_craftitem("moreores:mithril_ingot", {
	description = S("%s Ingot"):format(S("Mithril")),
	inventory_image = "moreores_mithril_ingot.png",
})

-- Helpers

local c = "moreores:silver_ingot"
local m = "moreores:mithril_ingot"

-- Cooking Ores into Ingots

minetest.register_craft({
	type = "cooking",
	output = c,
	recipe = "moreores:silver_lump",
})

minetest.register_craft({
	type = "cooking",
	output = m,
	recipe = "moreores:mithril_lump",
})

-- Blocks

minetest.register_node("moreores:silver_block", {
	description = S("%s Block"):format(S("Silver")),
	tiles = {"moreores_silver_block.png"},
	groups = {snappy = 1, bendy = 2, cracky = 1, melty = 2, level= 2},
	sounds = default.node_sound_metal_defaults(),
})

minetest.register_node("moreores:mithril_block", {
	description = S("%s Block"):format(S("Mithril")),
	tiles = {"moreores_mithril_block.png"},
	groups = {snappy = 1, bendy = 2, cracky = 1, melty = 2, level= 2},
	sounds = default.node_sound_metal_defaults(),
})

-- Ingot to Block Crafts and vice-versa

minetest.register_craft( {
	output = "moreores:silver_block",
	recipe = {{c, c, c}, {c, c, c}, {c, c, c}},
})

minetest.register_craft( {
	output = c .. " 9",
	recipe = {{"moreores:silver_block"}}
})

minetest.register_craft( {
	output = "moreores:mithril_block",
	recipe = {{m, m, m}, {m, m, m}, {m, m, m}},
})

minetest.register_craft( {
	output = m .. " 9",
	recipe = {{"moreores:mithril_block"}}
})

-- Mapgen Ores

minetest.register_ore({
	ore_type = "scatter",
	ore = "moreores:mineral_silver",
	wherein = "default:stone",
	clust_scarcity = 16*16*16,
	clust_num_ores = 4,
	clust_size = 11,
	y_min = -31000,
	y_max = -512,
})

minetest.register_ore({
	ore_type = "scatter",
	ore = "moreores:mineral_mithril",
	wherein = "default:stone",
	clust_scarcity = 128*128*128,
	clust_num_ores = 1,
	clust_size = 11,
	y_min = -31000,
	y_max = -6500,
})

-- Silver Tools

minetest.register_tool("moreores:pick_silver", {
	description = S("%s Pickaxe"):format(S("Silver")),
	inventory_image = "moreores_tool_silverpick.png",
	tool_capabilities = {
		full_punch_interval = 1.0,
		max_drop_level = 3,
		groupcaps = {
			cracky = {times = {[1] = 2.60, [2] = 1.00, [3] = 0.60}, uses = 100, maxlevel = 1},
		},
		damage_groups = {fleshy = 6},
	},
	sound = {breaks = "default_tool_breaks"},
})

minetest.register_tool("moreores:shovel_silver", {
	description = S("%s Shovel"):format(S("Silver")),
	inventory_image = "moreores_tool_silvershovel.png",
	wield_image = "moreores_tool_silvershovel.png^[transformR90",
	tool_capabilities = {
		full_punch_interval = 1.0,
		max_drop_level = 3,
		groupcaps = {
			crumbly = {times = {[1] = 1.10, [2] = 0.40, [3] = 0.25}, uses = 100, maxlevel = 1},
		},
		damage_groups = {fleshy = 6},
	},
	sound = {breaks = "default_tool_breaks"},
})

minetest.register_tool("moreores:axe_silver", {
	description = S("%s Axe"):format(S("Silver")),
	inventory_image = "moreores_tool_silveraxe.png",
	tool_capabilities = {
		full_punch_interval = 1.0,
		max_drop_level = 3,
		groupcaps = {
			choppy = {times = {[1] = 2.50, [2] = 0.80, [3] = 0.50}, uses = 100, maxlevel = 1},
			fleshy = {times = {[2] = 1.10, [3] = 0.60}, uses = 100, maxlevel = 1}
		},
		damage_groups = {fleshy = 6},
	},
	sound = {breaks = "default_tool_breaks"},
})

minetest.register_tool("moreores:sword_silver", {
	description = S("%s Sword"):format(S("Silver")),
	inventory_image = "moreores_tool_silversword.png",
	tool_capabilities = {
		full_punch_interval = 1.0,
		max_drop_level = 3,
		groupcaps = {
			fleshy = {times = {[2] = 0.70, [3] = 0.30}, uses = 100, maxlevel = 1},
			snappy = {times = {[2] = 0.70, [3] = 0.30}, uses = 100, maxlevel = 1},
			choppy = {times = {[3] = 0.80}, uses = 100, maxlevel = 0}
		},
		damage_groups = {fleshy = 6},
	},
	sound = {breaks = "default_tool_breaks"},
})

-- Silver Tool Crafts

minetest.register_craft({
	output = "moreores:pick_silver",
	recipe = {{c, c, c}, {"", "group:stick", ""}, {"", "group:stick", ""}}
})

minetest.register_craft({
	output = "moreores:shovel_silver",
	recipe = {{c}, {"group:stick"}, {"group:stick"}}
})

minetest.register_craft({
	output = "moreores:axe_silver",
	recipe = {{c, c}, {c, "group:stick"}, {"", "group:stick"}}
})

minetest.register_craft({
	output = "moreores:sword_silver",
	recipe = {{c}, {c}, {"group:stick"}}
})

-- Mithril Tools

minetest.register_tool("moreores:pick_mithril", {
	description = S("%s Pickaxe"):format(S("Mithril")),
	inventory_image = "moreores_tool_mithrilpick.png",
	tool_capabilities = {
		full_punch_interval = 0.45,
		max_drop_level = 3,
		groupcaps = {
			cracky = {times = {[1] = 2.25, [2] = 0.55, [3] = 0.35}, uses = 200, maxlevel = 2},
		},
		damage_groups = {fleshy = 9},
	},
	sound = {breaks = "default_tool_breaks"},
})

minetest.register_tool("moreores:shovel_mithril", {
	description = S("%s Shovel"):format(S("Mithril")),
	inventory_image = "moreores_tool_mithrilshovel.png",
	wield_image = "moreores_tool_mithrilshovel.png^[transformR90",
	tool_capabilities = {
		full_punch_interval = 0.45,
		max_drop_level = 3,
		groupcaps = {
			crumbly = {times = {[1] = 0.70, [2] = 0.35, [3] = 0.20}, uses = 200, maxlevel = 2},
		},
		damage_groups = {fleshy = 9},
	},
	sound = {breaks = "default_tool_breaks"},
})

minetest.register_tool("moreores:axe_mithril", {
	description = S("%s Axe"):format(S("Mithril")),
	inventory_image = "moreores_tool_mithrilaxe.png",
	tool_capabilities = {
		full_punch_interval = 0.45,
		max_drop_level = 3,
		groupcaps = {
			choppy = {times = {[1] = 1.75, [2] = 0.45, [3] = 0.45}, uses = 200, maxlevel = 2},
			fleshy = {times = {[2] = 0.95, [3] = 0.30}, uses = 200, maxlevel = 1}
		},
		damage_groups = {fleshy = 9},
	},
	sound = {breaks = "default_tool_breaks"},
})

minetest.register_tool("moreores:sword_mithril", {
	description = S("%s Sword"):format(S("Mithril")),
	inventory_image = "moreores_tool_mithrilsword.png",
	tool_capabilities = {
		full_punch_interval = 0.45,
		max_drop_level = 3,
		groupcaps = {
			fleshy = {times = {[2] = 0.65, [3] = 0.25}, uses = 200, maxlevel = 2},
			snappy = {times = {[2] = 0.70, [3] = 0.25}, uses = 200, maxlevel = 2},
			choppy = {times = {[3] = 0.65}, uses = 200, maxlevel = 0}
		},
		damage_groups = {fleshy = 9},
	},
	sound = {breaks = "default_tool_breaks"},
})

-- Mithril Tool Crafts

minetest.register_craft({
	output = "moreores:pick_mithril",
	recipe = {{m, m, m}, {"", "group:stick", ""}, {"", "group:stick", ""}}
})

minetest.register_craft({
	output = "moreores:shovel_mithril",
	recipe = {{m}, {"group:stick"}, {"group:stick"}}
})

minetest.register_craft({
	output = "moreores:axe_mithril",
	recipe = {{m, m}, {m, "group:stick"}, {"", "group:stick"}}
})

minetest.register_craft({
	output = "moreores:sword_mithril",
	recipe = {{m}, {m}, {"group:stick"}}
})

-- Compatibility

minetest.register_alias("moreores:mineral_tin", "default:stone_with_tin")
minetest.register_alias("moreores:tin_ingot", "default:tin_ingot")
minetest.register_alias("moreores:tin_block", "default:tinblock")
minetest.register_alias("moreores:tin_lump", "default:tin_lump")

-- Toolranks Mod Support

local function add_tool(name, desc, afteruse)

	minetest.override_item(name, {
		original_description = desc,
		description = toolranks.create_description(desc, 0, 1),
		after_use = afteruse and toolranks.new_afteruse
	})
end

if minetest.get_modpath("toolranks") then

	add_tool("moreores:pick_silver", S("%s Pickaxe"):format(S("Silver")), true)
	add_tool("moreores:axe_silver", S("%s Axe"):format(S("Silver")), true)
	add_tool("moreores:shovel_silver", S("%s Shovel"):format(S("Silver")), true)
	add_tool("moreores:sword_silver", S("%s Sword"):format(S("Silver")), true)

	add_tool("moreores:pick_mithril", S("%s Pickaxe"):format(S("Mithril")), true)
	add_tool("moreores:axe_mithril", S("%s Axe"):format(S("Mithril")), true)
	add_tool("moreores:shovel_mithril", S("%s Shovel"):format(S("Mithril")), true)
	add_tool("moreores:sword_mithril", S("%s Sword"):format(S("Mithril")), true)
end

print ("[MOD] moreores " .. S("loaded"))


code:
# Translation by Xanthin

loaded = geladen

%s Ore = %serz
%s Lump = %sklumpen
%s Ingot = %sbarren
%s Block = %sblock
%s Pickaxe = %sspitzhacke
%s Shovel = %sschaufel
%s Axe = %saxt
%s Sword = %sschwert
%s Hoe = %shacke

Silver = Silber
Mithril = Mithril

repo_name: korkut/MeseCraft

license: mit


code:
# Translation by kaeza

loaded = cargado

%s Ore = Mineral de %s
%s Lump = Pepita de %s
%s Ingot = Lingote de %s
%s Block = Bloque de %s
%s Pickaxe = Pico de %s
%s Shovel = Pala de %s
%s Axe = Hacha de %s
%s Sword = Espada de %s

Silver = plata
Mithril = mitrilo


code:
# Translation by Calinou

loaded = a été chargé

%s Ore = Minerai en %s
%s Lump = Roche en %s
%s Ingot = Lingot en %s
%s Block = Bloc en %s
%s Pickaxe = Pioche en %s
%s Shovel = Pelle en %s
%s Axe = Hache en %s
%s Sword = Épée en %s

Silver = argent
Mithril = mithril


code:
# Translation by Pagliaccio

loaded = caricato

%s Ore = Minerale di %s
%s Lump = %s grezzo
%s Ingot = Lingotto di %s
%s Block = Blocco di %s
%s Pickaxe = Piccone di %s
%s Shovel = Badile di %s
%s Axe = Ascia di %s
%s Sword = Spada di %s

Silver = Argento
Mithril = Mithril


code:
# Translation by Mahmutelmas06
# mahmutelmas06@hotmail.com
# Türkçe Çeviri
# Turkish translation
# Language 2 letter iso code is "tr"

loaded = yüklendi

%s Ore = %s madeni
%s Lump = %s yığını
%s Ingot = %s külçesi
%s Block = %s blok
%s Pickaxe = %s kazma
%s Shovel = %s kürek
%s Axe = %s balta
%s Sword = %s kılıç

Silver = Gümüş
Mithril = Mithril


code:
name = moreores
description = Adds more ores.
depends = default
optional_depends = intllib, toolranks
author = Calinou
title = More Ores


code:
# More Ores

More Ores for [Minetest](http://minetest.net), a free and open source infinite
world block sandbox game.

To install, just clone this repository into your "mods" directory.

More Ores code is licensed under MIT license, textures are by Calinou and are licensed under CC BY-SA 3.0 Unported.

**Forum topic:** <https://forum.minetest.net/viewtopic.php?f=11&t=549>

Notice: This is a re-write of more ores which has only silver and mithril ores
as well as tools.


code:
unused_args = false
allow_defined_top = true

globals = {
	"bucket",
	"nether"
}

read_globals = {
	"climate_api",
	"core",
	"default",
	"DIR_DELIM",
	"dump",
	"dungeon_loot",
	"intllib",
	"ItemStack",
	"loot",
	"doc",
	"math.hypot",
	"mesecon",
	"minetest",
	"nodeupdate",
	"PcgRandom",
	"PseudoRandom",
	"stairs",
	"stairsplus",
	"string.split",
	table = { fields = { "copy", "getn" } },
	"vector",
	"VoxelArea",
	"VoxelManip",
}


code:
# Nether Mod for Minetest, with Portals API.

Allows Nether portals to be constructed, opening a gateway between the surface
realm and one of lava and netherrack, with rumors of a passageway through the
netherrack to a great magma ocean.

To view the options provided by this mod, see settingtypes.txt or
go to "Settings"->"All Settings"->"Mods"->"nether" in the game.

A Nether portal is built as a rectangular vertical frame of obsidian, 4 blocks
wide and 5 blocks high. Once constructed, a Mese crystal fragment can be
right-click/used on the frame to activate it.

## Modders and game designers

See portal_api.txt for how to create custom portals to your own realms.

This mod provides Nether basalts (natural, hewn, and chiseled) as nodes which
require a player to journey to the magma ocean to obtain, so these can be used
for gating progression through a game. For example, a portal to another realm
might need to be constructed from basalt, thus requiring a journey through
the nether first, or basalt might be a crafting ingredient required to reach
a particular branch of the tech-tree.

Netherbrick tools are provided (pick, shovel, axe, & sword), see tools.lua

Nether Portals can allow surface fast-travel.

## License of source code:

Copyright (C) 2013 PilzAdam

Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

## License and attribution of media (textures and sounds)

### [Public Domain Dedication (CC0 1.0)](https://creativecommons.org/publicdomain/zero/1.0/)

 * `nether_portal_teleport.ogg` is a timing adjusted version of "teleport" by [outroelison](https://freesound.org/people/outroelison), used under CC0 1.0

### [Attribution 3.0 Unported (CC BY 3.0)](https://creativecommons.org/licenses/by/3.0/)

 * `nether_portal_ambient.ogg` & `nether_portal_ambient.0.ogg` are extractions from "Deep Cinematic Rumble Stereo" by [Patrick Lieberkind](http://www.lieberkindvisuals.dk), used under CC BY 3.0
 * `nether_portal_extinguish.ogg` is an extraction from "Tight Laser Weapon Hit Scifi" by [damjancd](https://freesound.org/people/damjancd), used under CC BY 3.0
 * `nether_portal_ignite.ogg` is a derivative of "Flame Ignition" by [hykenfreak](https://freesound.org/people/hykenfreak), used under CC BY 3.0. "Nether Portal ignite" is licensed under CC BY 3.0 by Treer.

### [Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/)
 * `nether_basalt`* (files starting with "nether_basalt"): Treer, 2020
 * `nether_book_`* (files starting with "nether_book"): Treer, 2019-2020
 * `nether_fumarole.ogg`: Treer, 2020
 * `nether_lava_bubble`* (files starting with "nether_lava_bubble"): Treer, 2020
 * `nether_lava_crust_animated.png`: Treer, 2019-2020
 * `nether_particle_anim`* (files starting with "nether_particle_anim"): Treer, 2019
 * `nether_portal_ignition_failure.ogg`: Treer, 2019
 * `nether_smoke_puff.png`: Treer, 2020

### [Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)](http://creativecommons.org/licenses/by-sa/3.0/)
 * `nether_glowstone`* (files starting with "nether_glowstone"): BlockMen
 * `nether_nether_ingot.png` & `nether_nether_lump.png`: color adjusted versions from "[default](https://github.com/minetest/minetest_game/tree/master/mods/default)" mod, originally by Gambit
 * `nether_portal.png`: [Extex101](https://github.com/Extex101), 2020
 * `nether_rack`* (files starting with "nether_rack"): Zeg9
 * `nether_tool_`* (files starting with "nether_tool_"): color adjusted versions from "[default](https://github.com/minetest/minetest_game/tree/master/mods/default)" mod, originals by BlockMen

All other media: Copyright © 2013 PilzAdam, licensed under CC BY-SA 3.0 by PilzAdam.


code:
--[[

  Copyright (C) 2013 PilzAdam
  Copyright (C) 2020 lortas
  Copyright (C) 2020 Treer

  Permission to use, copy, modify, and/or distribute this software for
  any purpose with or without fee is hereby granted, provided that the
  above copyright notice and this permission notice appear in all copies.

  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
  BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
  OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  SOFTWARE.

]]--

minetest.register_craft({
	output = "nether:brick 4",
	recipe = {
		{"nether:rack", "nether:rack"},
		{"nether:rack", "nether:rack"},
	}
})

minetest.register_craft({
	output = "nether:fence_nether_brick 6",
	recipe = {
		{"nether:brick", "nether:brick", "nether:brick"},
		{"nether:brick", "nether:brick", "nether:brick"},
	},
})

minetest.register_craft({
	output = "nether:brick_compressed",
	recipe = {
		{"nether:brick","nether:brick","nether:brick"},
		{"nether:brick","nether:brick","nether:brick"},
		{"nether:brick","nether:brick","nether:brick"},
	}
})

minetest.register_craft({
    output = "nether:basalt_hewn",
    type = "shapeless",
    recipe = {
      "nether:basalt",
      "nether:basalt",
    },
})

minetest.register_craft({
  output = "nether:basalt_chiselled 4",
  recipe = {
    {"nether:basalt_hewn", "nether:basalt_hewn"},
    {"nether:basalt_hewn", "nether:basalt_hewn"}
  }
})

-- See tools.lua for tools related crafting


code:
default
stairs
doc_basics?
dungeon_loot?
fire?
loot?
mesecons?
moreblocks?
climate_api?


code: Adds a deep underground realm with different mapgen that you can reach with obsidian portals.


code:
--[[

  Nether mod for minetest

  Copyright (C) 2013 PilzAdam

  Permission to use, copy, modify, and/or distribute this software for
  any purpose with or without fee is hereby granted, provided that the
  above copyright notice and this permission notice appear in all copies.

  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
  BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
  OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  SOFTWARE.

]]--

-- Set DEBUG_FLAGS to determine the behavior of nether.debug():
--   0 = off
--   1 = print(...)
--   2 = minetest.chat_send_all(...)
--   4 = minetest.log("info", ...)
local DEBUG_FLAGS = 0

local S
if minetest.get_translator ~= nil then
	S = minetest.get_translator("nether")
else
	-- mock the translator function for MT 0.4
	S = function(str, ...)
		local args={...}
		return str:gsub(
			"@%d+",
			function(match) return args[tonumber(match:sub(2))]	end
		)
	end
end

-- Global Nether namespace
nether                = {}
nether.mapgen         = {} -- Shared Nether mapgen namespace, for mapgen files to expose functions and constants
nether.modname        = minetest.get_current_modname()
nether.path           = minetest.get_modpath(nether.modname)
nether.get_translator = S
                     -- nether.useBiomes allows other mods to know whether they can register ores etc. in the Nether.
                     -- See mapgen.lua for an explanation of why minetest.read_schematic is being checked
nether.useBiomes      = minetest.get_mapgen_setting("mg_name") ~= "v6" and minetest.read_schematic ~= nil
nether.fogColor = {	           -- only used if climate_api is installed
	netherCaverns = "#1D0504", -- Distance-fog colour for classic nether
	mantle        = "#070916", -- Distance-fog colour for the Mantle region
	geodes        = "#300530"  -- Distance-fog colour for secondary region
}

-- Settings
nether.DEPTH_CEILING              =  -5000 -- The y location of the Nether's celing
nether.DEPTH_FLOOR                = -11000 -- The y location of the Nether's floor
nether.FASTTRAVEL_FACTOR          =      8 -- 10 could be better value for Minetest, since there's no sprint, but ex-Minecraft players will be mathing for 8
nether.PORTAL_BOOK_LOOT_WEIGHTING =    0.9 -- Likelyhood of finding the Book of Portals (guide) in dungeon chests. Set to 0 to disable.
nether.NETHER_REALM_ENABLED       =   true -- Setting to false disables the Nether and Nether portal

-- Override default settings with values from the .conf file, if any are present.
nether.FASTTRAVEL_FACTOR          = tonumber(minetest.settings:get("nether_fasttravel_factor") or nether.FASTTRAVEL_FACTOR)
nether.PORTAL_BOOK_LOOT_WEIGHTING = tonumber(minetest.settings:get("nether_portalBook_loot_weighting") or nether.PORTAL_BOOK_LOOT_WEIGHTING)
nether.NETHER_REALM_ENABLED       = minetest.settings:get_bool("nether_realm_enabled", nether.NETHER_REALM_ENABLED)
nether.DEPTH_CEILING              = tonumber(minetest.settings:get("nether_depth_ymax") or nether.DEPTH_CEILING)
nether.DEPTH_FLOOR                = tonumber(minetest.settings:get("nether_depth_ymin") or nether.DEPTH_FLOOR)

if nether.DEPTH_FLOOR + 1000 > nether.DEPTH_CEILING then
	error("The lower limit of the Nether must be set at least 1000 lower than the upper limit, and more than 3000 is recommended. Set settingtypes.txt, or 'All Settings' -> 'Mods' -> 'nether' -> 'Nether depth'", 0)
end
nether.DEPTH = nether.DEPTH_CEILING -- Deprecated, use nether.DEPTH_CEILING instead.

-- A debug-print function that understands vectors etc. and does not
-- evaluate when debugging is turned off.
-- Works like string.format(), treating the message as a format string.
-- nils, tables, and vectors passed as arguments to nether.debug() are
-- converted to strings and can be included inside the message with %s
function nether.debug(message, ...)

	local args = {...}
	local argCount = select("#", ...)

	for i = 1, argCount do
		local arg = args[i]
		if arg == nil then
			-- convert nils to strings
			args[i] = '<nil>'
		elseif type(arg) == "table" then
			local tableCount = 0
			for _,_ in pairs(arg) do tableCount = tableCount + 1 end
			if tableCount == 3 and arg.x ~= nil and arg.y ~= nil and arg.z ~= nil then
				-- convert vectors to strings
				args[i] = minetest.pos_to_string(arg)
			else
				-- convert tables to strings
				-- (calling function can use dump() if a multi-line listing is desired)
				args[i] = string.gsub(dump(arg, ""), "\n", " ")
			end
		end
	end

	local composed_message = "nether: " .. string.format(message, unpack(args))

	if math.floor(DEBUG_FLAGS / 1) % 2 == 1 then print(composed_message) end
	if math.floor(DEBUG_FLAGS / 2) % 2 == 1 then minetest.chat_send_all(composed_message) end
	if math.floor(DEBUG_FLAGS / 4) % 2 == 1 then minetest.log("info", composed_message) end
end
if DEBUG_FLAGS == 0 then
	-- do as little evaluation as possible
	nether.debug = function() end
end

-- Load files
dofile(nether.path .. "/portal_api.lua")
dofile(nether.path .. "/nodes.lua")
dofile(nether.path .. "/tools.lua")
dofile(nether.path .. "/crafts.lua")
if nether.NETHER_REALM_ENABLED then
	if nether.useBiomes then
		dofile(nether.path .. "/mapgen.lua")
	else
		dofile(nether.path .. "/mapgen_nobiomes.lua")
	end
end
dofile(nether.path .. "/portal_examples.lua")

-- Portals are ignited by right-clicking with a mese crystal fragment
nether.register_portal_ignition_item(
	"default:mese_crystal_fragment",
	{name = "nether_portal_ignition_failure", gain = 0.3}
)

if nether.NETHER_REALM_ENABLED then
	-- Use the Portal API to add a portal type which goes to the Nether
	-- See portal_api.txt for documentation
	nether.register_portal("nether_portal", {
		shape               = nether.PortalShape_Traditional,
		frame_node_name     = "default:obsidian",
		wormhole_node_color = 0, -- 0 is magenta
		title = S("Nether Portal"),
		book_of_portals_pagetext = S([[Construction requires 14 blocks of obsidian, which we found deep underground where water had solidified molten rock. The finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway.

This opens to a truly hellish place, though for small mercies the air there is still breathable. There is an intriguing dimensional mismatch happening between this realm and ours, as after opening the second portal into it we observed that 10 strides taken in the Nether appear to be an equivalent of @1 in the natural world.

The expedition parties have found no diamonds or gold, and after an experienced search party failed to return from the trail of a missing expedition party, I must conclude this is a dangerous place.]], 10 * nether.FASTTRAVEL_FACTOR),

		is_within_realm = function(pos) -- return true if pos is inside the Nether
			return pos.y < nether.DEPTH_CEILING and pos.y > nether.DEPTH_FLOOR
		end,

		find_realm_anchorPos = function(surface_anchorPos, player_name)
			-- divide x and z by a factor of 8 to implement Nether fast-travel
			local destination_pos = vector.divide(surface_anchorPos, nether.FASTTRAVEL_FACTOR)
			destination_pos.x = math.floor(0.5 + destination_pos.x) -- round to int
			destination_pos.z = math.floor(0.5 + destination_pos.z) -- round to int
			destination_pos.y = nether.DEPTH_CEILING - 1000 -- temp value so find_nearest_working_portal() returns nether portals

			-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Nether)
			local existing_portal_location, existing_portal_orientation =
				nether.find_nearest_working_portal("nether_portal", destination_pos, 8, 0)

			if existing_portal_location ~= nil then
				return existing_portal_location, existing_portal_orientation
			else
				local start_y = nether.DEPTH_CEILING - math.random(500, 1500) -- Search starting altitude
				destination_pos.y = nether.find_nether_ground_y(destination_pos.x, destination_pos.z, start_y, player_name)
				return destination_pos
			end
		end,

		find_surface_anchorPos = function(realm_anchorPos, player_name)
			-- A portal definition doesn't normally need to provide a find_surface_anchorPos() function,
			-- since find_surface_target_y() will be used by default, but Nether portals also scale position
			-- to create fast-travel.
			-- Defining a custom function also means we can look for existing nearby portals.

			-- Multiply x and z by a factor of 8 to implement Nether fast-travel
			local destination_pos = vector.multiply(realm_anchorPos, nether.FASTTRAVEL_FACTOR)
			destination_pos.x = math.min(30900, math.max(-30900, destination_pos.x)) -- clip to world boundary
			destination_pos.z = math.min(30900, math.max(-30900, destination_pos.z)) -- clip to world boundary
			destination_pos.y = 0 -- temp value so find_nearest_working_portal() doesn't return nether portals

			-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are outside the Nether)
			local existing_portal_location, existing_portal_orientation =
				nether.find_nearest_working_portal("nether_portal", destination_pos, 8 * nether.FASTTRAVEL_FACTOR, 0)

			if existing_portal_location ~= nil then
				return existing_portal_location, existing_portal_orientation
			else
				destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "nether_portal", player_name)
				return destination_pos
			end
		end,

		on_ignite = function(portalDef, anchorPos, orientation)

			-- make some sparks fly
			local p1, p2 = portalDef.shape:get_p1_and_p2_from_anchorPos(anchorPos, orientation)
			local pos = vector.divide(vector.add(p1, p2), 2)

			local textureName = portalDef.particle_texture
			if type(textureName) == "table" then textureName = textureName.name end

			minetest.add_particlespawner({
				amount = 110,
				time   = 0.1,
				minpos = {x = pos.x - 0.5, y = pos.y - 1.2, z = pos.z - 0.5},
				maxpos = {x = pos.x + 0.5, y = pos.y + 1.2, z = pos.z + 0.5},
				minvel = {x = -5, y = -1, z = -5},
				maxvel = {x =  5, y =  1, z =  5},
				minacc = {x =  0, y =  0, z =  0},
				maxacc = {x =  0, y =  0, z =  0},
				minexptime = 0.1,
				maxexptime = 0.5,
				minsize = 0.2 * portalDef.particle_texture_scale,
				maxsize = 0.8 * portalDef.particle_texture_scale,
				collisiondetection = false,
				texture = textureName .. "^[colorize:#F4F:alpha",
				animation = portalDef.particle_texture_animation,
				glow = 8
			})

		end

	})

	-- Set appropriate nether distance-fog if climate_api is available
	--
	-- Delegating to a mod like climate_api means nether won't unexpectedly stomp on the sky of
	-- any other mod.
	-- Skylayer is another mod which can perform this role, and skylayer support could be added
	-- here as well. However skylayer doesn't provide a position-based method of specifying sky
	-- colours out-of-the-box, so the nether mod will have to monitor when players enter and
	-- leave the nether.
	if minetest.get_modpath("climate_api") and minetest.global_exists("climate_api") and climate_api.register_weather ~= nil then

		climate_api.register_influence(
			"nether_biome",
			function(pos)
				local result = "surface"

				if pos.y <= nether.DEPTH_CEILING and pos.y >= nether.DEPTH_FLOOR then
					result = "nether"

					-- since mapgen_nobiomes.lua has no regions it doesn't implement getRegion(),
					-- so only use getRegion() if it exists
					if nether.mapgen.getRegion ~= nil then
						-- the biomes-based mapgen supports 2 extra regions
						local regions = nether.mapgen.RegionEnum
						local region  = nether.mapgen.getRegion(pos)
						if region == regions.CENTER or region == regions.CENTERSHELL then
							result = "mantle"
						elseif region == regions.NEGATIVE or region == regions.NEGATIVESHELL then
							result = "geode"
						end
					end
				end

				return result
			end
		)

		-- using sky type "plain" unfortunately means we don't get smooth fading transitions when
		-- the color of the sky changes, but it seems to be the only way to obtain a sky colour
		-- which doesn't brighten during the daytime.
		local undergroundSky = {
			sky_data = {
				base_color = nil,
				type = "plain",
				textures = nil,
				clouds = false,
			},
			sun_data = {
				visible = false,
				sunrise_visible = false
			},
			moon_data = {
				visible = false
			},
			star_data = {
				visible = false
			}
		}

		local netherSky, mantleSky, geodeSky = table.copy(undergroundSky), table.copy(undergroundSky), table.copy(undergroundSky)
		netherSky.sky_data.base_color = nether.fogColor.netherCaverns
		mantleSky.sky_data.base_color = nether.fogColor.mantle
		geodeSky.sky_data.base_color  = nether.fogColor.geodes

		climate_api.register_weather(
			"nether:nether",
			{ nether_biome = "nether" },
			{ ["climate_api:skybox"] = netherSky }
		)

		climate_api.register_weather(
			"nether:mantle",
			{ nether_biome = "mantle" },
			{ ["climate_api:skybox"] = mantleSky }
		)

		climate_api.register_weather(
			"nether:geode",
			{ nether_biome = "geode" },
			{ ["climate_api:skybox"] = geodeSky }
		)
	end

end -- end of "if nether.NETHER_REALM_ENABLED..."

-- Play bubbling lava sounds if player killed by lava
minetest.register_on_dieplayer(
	function(player, reason)
		if reason.node ~= nil and minetest.get_node_group(reason.node, "lava") > 0 or reason.node == "nether:lava_crust" then
			minetest.sound_play(
				"nether_lava_bubble",
				-- this sample was encoded at 3x speed to reduce .ogg file size
				-- at the expense of higher frequencies, so pitch it down ~3x
				{to_player = player:get_player_name(), pitch = 0.3, gain = 0.8}
			)
		end
	end
)


code:
--[[

  Nether mod for minetest

  "mapgen.lua" is the modern biomes-based Nether mapgen, which
    requires Minetest v5.1 or greater
  "mapgen_nobiomes.lua" is the legacy version of the mapgen, only used
    in older versions of Minetest or in v6 worlds.

  Copyright (C) 2013 PilzAdam

  Permission to use, copy, modify, and/or distribute this software for
  any purpose with or without fee is hereby granted, provided that the
  above copyright notice and this permission notice appear in all copies.

  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
  BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
  OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  SOFTWARE.

]]--

-- Parameters

local NETHER_CEILING = nether.DEPTH_CEILING
local NETHER_FLOOR   = nether.DEPTH_FLOOR
local TCAVE = 0.6
local BLEND = 128

-- parameters for central region
local REGION_BUFFER_THICKNESS   = 0.2
local CENTER_REGION_LIMIT       = TCAVE - REGION_BUFFER_THICKNESS -- Netherrack gives way to Deep-Netherrack here
local CENTER_CAVERN_LIMIT       = CENTER_REGION_LIMIT - 0.1       -- Deep-Netherrack gives way to air here
local SURFACE_CRUST_LIMIT       = CENTER_CAVERN_LIMIT * 0.25      -- Crusted-lava at the surface of the lava ocean gives way to liquid lava here
local CRUST_LIMIT               = CENTER_CAVERN_LIMIT * 0.85      -- Crusted-lava under the surface of the lava ocean gives way to liquid lava here
local BASALT_COLUMN_UPPER_LIMIT = CENTER_CAVERN_LIMIT * 0.9       -- Basalt columns may appear between these upper and lower limits
local BASALT_COLUMN_LOWER_LIMIT = CENTER_CAVERN_LIMIT * 0.25      -- This value is close to SURFACE_CRUST_LIMIT so basalt columns give way to "flowing" lava rivers

-- Shared Nether mapgen namespace
-- For mapgen files to share functions and constants
local mapgen = nether.mapgen

mapgen.TCAVE                     = TCAVE                     -- const needed in mapgen_mantle.lua
mapgen.BLEND                     = BLEND                     -- const needed in mapgen_mantle.lua
mapgen.CENTER_REGION_LIMIT       = CENTER_REGION_LIMIT       -- const needed in mapgen_mantle.lua
mapgen.CENTER_CAVERN_LIMIT       = CENTER_CAVERN_LIMIT       -- const needed in mapgen_mantle.lua
mapgen.BASALT_COLUMN_UPPER_LIMIT = BASALT_COLUMN_UPPER_LIMIT -- const needed in mapgen_mantle.lua
mapgen.BASALT_COLUMN_LOWER_LIMIT = BASALT_COLUMN_LOWER_LIMIT -- const needed in mapgen_mantle.lua

mapgen.ore_ceiling = NETHER_CEILING - BLEND -- leave a solid 128 node cap of netherrack before introducing ores
mapgen.ore_floor   = NETHER_FLOOR   + BLEND

local debugf = nether.debug

if minetest.read_schematic == nil then
	-- Using biomes to create the Nether requires the ability for biomes to set "node_cave_liquid = air".
	-- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, but we can't test for
	-- it directly. However b2065756c was merged a few months later (in 2019-08-14) and it is easy
	-- to directly test for - it adds minetest.read_schematic() - so we use this as a proxy-test
	-- for whether the Minetest engine is recent enough to have implemented node_cave_liquid=air
	error("This " .. nether.modname .. " mapgen requires Minetest v5.1 or greater, use mapgen_nobiomes.lua instead.", 0)
end

-- Load specialty helper functions
dofile(nether.path .. "/mapgen_dungeons.lua")
dofile(nether.path .. "/mapgen_mantle.lua")

-- Misc math functions

-- avoid needing table lookups each time a common math function is invoked
local math_max, math_min, math_abs, math_floor = math.max, math.min, math.abs, math.floor

-- Inject nether_caverns biome

local function override_underground_biomes()
	-- https://forum.minetest.net/viewtopic.php?p=257522#p257522
	-- Q: Is there a way to override an already-registered biome so I can get it out of the
	--    way of my own underground biomes without disturbing the other biomes registered by
	--    default?
	-- A: No, all you can do is use a mod to clear all biomes then re-register the complete
	--    set but with your changes. It has been described as hacky but this is actually the
	--    official way to alter biomes, most mods and subgames would want to completely change
	--    all biomes anyway.
	--    To avoid the engine side of mapgen becoming overcomplex the approach is to require mods
	--    to do slightly more complex stuff in Lua.

	-- take a copy of all biomes, decorations, and ores. Regregistering a biome changes its ID, so
	-- any decorations or ores using the 'biomes' field must afterwards be cleared and re-registered.
	-- https://github.com/minetest/minetest/issues/9288
	local registered_biomes_copy      = {}
	local registered_decorations_copy = {}
	local registered_ores_copy        = {}

	for old_biome_key, old_biome_def in pairs(minetest.registered_biomes) do
	   registered_biomes_copy[old_biome_key] = old_biome_def
	end
	for old_decoration_key, old_decoration_def in pairs(minetest.registered_decorations) do
	   registered_decorations_copy[old_decoration_key] = old_decoration_def
	end
	for old_ore_key, old_ore_def in pairs(minetest.registered_ores) do
		registered_ores_copy[old_ore_key] = old_ore_def
	end

	-- clear biomes, decorations, and ores
	minetest.clear_registered_decorations()
	minetest.clear_registered_ores()
	minetest.clear_registered_biomes()

	-- Restore biomes, adjusted to not overlap the Nether
	for biome_key, new_biome_def in pairs(registered_biomes_copy) do
		-- follow similar min_pos/max_pos processing logic as read_biome_def() in l_mapgen.cpp
		local biome_y_max, biome_y_min = 31000, -31000
		if type(new_biome_def.min_pos) == 'table' and type(new_biome_def.min_pos.y) == 'number' then biome_y_min = new_biome_def.min_pos.y end
		if type(new_biome_def.max_pos) == 'table' and type(new_biome_def.max_pos.y) == 'number' then biome_y_max = new_biome_def.max_pos.y end
		if type(new_biome_def.y_min) == 'number' then biome_y_min = new_biome_def.y_min end
		if type(new_biome_def.y_max) == 'number' then biome_y_max = new_biome_def.y_max end

		if biome_y_max > NETHER_FLOOR and biome_y_min < NETHER_CEILING then
			-- This biome occupies some or all of the depth of the Nether, shift/crop it.
			local new_y_min, new_y_max
			local spaceOccupiedAbove = biome_y_max - NETHER_CEILING
			local spaceOccupiedBelow = NETHER_FLOOR - biome_y_min
			if spaceOccupiedAbove >= spaceOccupiedBelow or biome_y_min <= -30000 then
				-- place the biome above the Nether
				-- We also shift biomes which extend to the bottom of the map above the Nether, since they
				-- likely only extend that deep as a catch-all, and probably have a role nearer the surface.
				new_y_min = NETHER_CEILING + 1
				new_y_max = math_max(biome_y_max, NETHER_CEILING + 2)
			else
				-- shift the biome to below the Nether
				new_y_max = NETHER_FLOOR - 1
				new_y_min = math_min(biome_y_min, NETHER_CEILING - 2)
			end

			debugf("Moving biome \"%s\" from %s..%s to %s..%s", new_biome_def.name, new_biome_def.y_min, new_biome_def.y_max, new_y_min, new_y_max)

			if type(new_biome_def.min_pos) == 'table' and type(new_biome_def.min_pos.y) == 'number' then new_biome_def.min_pos.y = new_y_min end
			if type(new_biome_def.max_pos) == 'table' and type(new_biome_def.max_pos.y) == 'number' then new_biome_def.max_pos.y = new_y_max end
			new_biome_def.y_min = new_y_min -- Ensure the new heights are saved, even if original biome never specified one
			new_biome_def.y_max = new_y_max
		end
		minetest.register_biome(new_biome_def)
	end

	-- Restore biome decorations
	for decoration_key, new_decoration_def in pairs(registered_decorations_copy) do
	   minetest.register_decoration(new_decoration_def)
	end
	-- Restore biome ores
	for ore_key, new_ore_def in pairs(registered_ores_copy) do
		minetest.register_ore(new_ore_def)
	 end
 end

-- Shift any overlapping biomes out of the way before we create the Nether biomes
override_underground_biomes()

-- nether:native_mapgen is used to prevent ores and decorations being generated according
-- to landforms created by the native mapgen.
-- Ores and decorations can be registered against "nether:rack" instead, and the lua
-- on_generate() callback will carve the Nether with nether:rack before invoking
-- generate_decorations and generate_ores.
minetest.register_node("nether:native_mapgen", {})

minetest.register_biome({
	name = "nether_caverns",
	node_stone  = "nether:native_mapgen", -- nether:native_mapgen is used here to prevent the native mapgen from placing ores and decorations.
	node_filler = "nether:native_mapgen", -- The lua on_generate will transform nether:native_mapgen into nether:rack then decorate and add ores.
	node_dungeon = "nether:brick",
	node_dungeon_alt = "nether:brick_cracked",
	node_dungeon_stair = "stairs:stair_nether_brick",
	-- Setting node_cave_liquid to "air" avoids the need to filter lava and water out of the mapchunk and
	-- surrounding shell (overdraw nodes beyond the mapchunk).
	-- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, and this mapgen.lua file should only
	-- be run if the Minetest version includes it. The earliest tag made after 2019-05-19 is 5.1.0 on 2019-10-13,
	-- however we shouldn't test version numbers. minetest.read_schematic() was added by b2065756c and merged in
	-- 2019-08-14 and is easy to test for, we don't use it but it should make a good proxy-test for whether the
	-- Minetest version is recent enough to have implemented node_cave_liquid=air
	node_cave_liquid = "air",
	y_max = NETHER_CEILING,
	y_min = NETHER_FLOOR,
	vertical_blend = 0,
	heat_point = 50,
	humidity_point = 50,
})

-- Ores and decorations

dofile(nether.path .. "/mapgen_decorations.lua")

minetest.register_ore({
	ore_type       = "scatter",
	ore            = "nether:glowstone",
	wherein        = "nether:rack",
	clust_scarcity = 11 * 11 * 11,
	clust_num_ores = 3,
	clust_size     = 2,
	y_max = mapgen.ore_ceiling,
	y_min = mapgen.ore_floor
})

minetest.register_ore({
	ore_type       = "scatter",
	ore            = "nether:lava_crust", -- crusted lava replaces scattered glowstone in the mantle
	wherein        = "nether:rack_deep",
	clust_scarcity = 16 * 16 * 16,
	clust_num_ores = 4,
	clust_size     = 2,
	y_max = mapgen.ore_ceiling,
	y_min = mapgen.ore_floor
})

minetest.register_ore({
	ore_type       = "scatter",
	ore            = "default:lava_source",
	wherein        = {"nether:rack", "nether:rack_deep"},
	clust_scarcity = 36 * 36 * 36,
	clust_num_ores = 4,
	clust_size     = 2,
	y_max = mapgen.ore_ceiling,
	y_min = mapgen.ore_floor
})

minetest.register_ore({
	ore_type        = "blob",
	ore             = "nether:sand",
	wherein         = "nether:rack",
	clust_scarcity  = 14 * 14 * 14,
	clust_size      = 8,
	y_max = mapgen.ore_ceiling,
	y_min = mapgen.ore_floor
})

-- Mapgen

-- 3D noise

mapgen.np_cave = {
	offset = 0,
	scale = 1,
	spread = {x = 384, y = 128, z = 384}, -- squashed 3:1
	seed = 59033,
	octaves = 5,
	persist = 0.7,
	lacunarity = 2.0,
	--flags = ""
}

local cavePointPerlin = nil

mapgen.getCavePointPerlin = function()
	cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
	return cavePointPerlin
end

mapgen.getCavePerlinAt = function(pos)
	cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
	return cavePointPerlin:get_3d(pos)
end

-- Buffers and objects we shouldn't recreate every on_generate

local nobj_cave = nil
local nbuf_cave = {}
local dbuf = {}

-- Content ids

local c_air              = minetest.get_content_id("air")
local c_netherrack       = minetest.get_content_id("nether:rack")
local c_netherrack_deep  = minetest.get_content_id("nether:rack_deep")
local c_lavasea_source   = minetest.get_content_id("nether:lava_source") -- same as lava but with staggered animation to look better as an ocean
local c_lava_crust       = minetest.get_content_id("nether:lava_crust")
local c_native_mapgen    = minetest.get_content_id("nether:native_mapgen")

local yblmin = NETHER_FLOOR   + BLEND * 2
local yblmax = NETHER_CEILING - BLEND * 2
-- At both the top and bottom of the Nether, as set by NETHER_CEILING and NETHER_FLOOR,
-- there is a 128 deep cap of solid netherrack, followed by a 128-deep blending zone
-- where Nether caverns may start to appear.
-- The solid zones and blending zones are achieved by adjusting the np_cave noise to be
-- outside the range where caverns form, this function returns that adjustment.
--
-- Returns two values: the noise limit adjustment for nether caverns, and the
-- noise limit adjustment for the central region / mantle caverns
mapgen.get_mapgenblend_adjustments = function(y)

	-- floorAndCeilingBlend will normally be 0, but shifts toward 1 in the
	-- blending zone, and goes higher than 1 in the solid zone between the
	-- blending zone and the end of the nether.
	local floorAndCeilingBlend = 0
	if y > yblmax then floorAndCeilingBlend = ((y - yblmax) / BLEND) ^ 2 end
	if y < yblmin then floorAndCeilingBlend = ((yblmin - y) / BLEND) ^ 2 end

	-- the nether caverns exist when np_cave noise is greater than TCAVE, so
	-- to fade out the nether caverns, adjust TCAVE upward.
	local tcave_adj               = floorAndCeilingBlend

	-- the central regions exists when np_cave noise is below CENTER_REGION_LIMIT,
	-- so to fade out the mantle caverns adjust CENTER_REGION_LIMIT downward.
	local centerRegionLimit_adj = -(CENTER_REGION_LIMIT * floorAndCeilingBlend)

	return tcave_adj, centerRegionLimit_adj
end

-- On-generated function

local tunnelCandidate_count = 0
local tunnel_count = 0
local total_chunk_count = 0
local function on_generated(minp, maxp, seed)

	if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then
		return
	end

	local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip")
	local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max}
	local data = vm:get_data(dbuf)

	local x0, y0, z0 = minp.x, math_max(minp.y, NETHER_FLOOR),   minp.z
	local x1, y1, z1 = maxp.x, math_min(maxp.y, NETHER_CEILING), maxp.z

	local yCaveStride = x1 - x0 + 1
	local zCaveStride = yCaveStride * yCaveStride
	local chulens = {x = yCaveStride, y = yCaveStride, z = yCaveStride}

	nobj_cave = nobj_cave or minetest.get_perlin_map(mapgen.np_cave, chulens)
	local nvals_cave = nobj_cave:get_3d_map_flat(minp, nbuf_cave)

	local dungeonRooms = mapgen.build_dungeon_room_list(data, area) -- function from mapgen_dungeons.lua
	local abs_cave_noise, abs_cave_noise_adjusted

	local contains_nether = false
	local contains_mantle = false
	local contains_ocean  = false

	for y = y0, y1 do -- Y loop first to minimise tcave & lava-sea calculations

		local sea_level, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(y) -- function from mapgen_mantle.lua
		local above_lavasea = y > sea_level
		local below_lavasea = y < sea_level

		local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(y)
		local tcave   = TCAVE + tcave_adj
		local tmantle = CENTER_REGION_LIMIT + centerRegionLimit_adj -- cavern_noise_adj already contains central_region_limit_adj, so tmantle is only for comparisons when cavern_noise_adj hasn't been added to the noise value

		 -- cavern_noise_adj gets added to noise value instead of added to the limit np_noise
		 -- is compared against, so subtract centerRegionLimit_adj instead of adding
		local cavern_noise_adj =
			CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance) -
			centerRegionLimit_adj

		for z = z0, z1 do
			local vi = area:index(x0, y, z) -- Initial voxelmanip index
			local ni = (z - z0) * zCaveStride + (y - y0) * yCaveStride + 1
			local noise2di = 1 + (z - z0) * yCaveStride

			for x = x0, x1 do

				local cave_noise = nvals_cave[ni]

				if cave_noise > tcave then
					-- Prime region
					-- This was the only region in initial versions of the Nether mod.
					-- It is the only region which portals from the surface will open into.
					data[vi] = c_air
					contains_nether = true

				elseif -cave_noise > tcave then
					-- Secondary/spare region
					-- This secondary region is unused until someone decides to do something cool or novel with it.
					-- Reaching here would require the player to first find and journey through the central region,
					-- as it's always separated from the Prime region by the central region.

					data[vi] = c_netherrack -- For now I've just left this region as solid netherrack instead of air.

					-- Only set contains_nether to true here if you want tunnels created between the secondary region
					-- and the central region.
					--contains_nether = true
					--data[vi] = c_air
				else
					-- netherrack walls and/or center region/mantle
					abs_cave_noise = math_abs(cave_noise)

					-- abs_cave_noise_adjusted makes the center region smaller as distance from the lava ocean
					-- increases, we do this by pretending the abs_cave_noise value is higher.
					abs_cave_noise_adjusted = abs_cave_noise + cavern_noise_adj

					if abs_cave_noise_adjusted >= CENTER_CAVERN_LIMIT then

						local id = data[vi] -- Check existing node to avoid removing dungeons
						if id == c_air or id == c_native_mapgen then
							if abs_cave_noise < tmantle then
								data[vi] = c_netherrack_deep
							else
								-- the shell seperating the mantle from the rest of the nether...
								data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons
							end
						end

					elseif above_lavasea then
						data[vi] = c_air
						contains_mantle = true
					elseif abs_cave_noise_adjusted < SURFACE_CRUST_LIMIT or (below_lavasea and abs_cave_noise_adjusted < CRUST_LIMIT) then
						data[vi] = c_lavasea_source
						contains_ocean = true
					else
						data[vi] = c_lava_crust
						contains_ocean = true
					end
				end

				ni = ni + 1
				vi = vi + 1
				noise2di = noise2di + 1
			end
		end
	end

	if contains_mantle or contains_ocean then
		mapgen.add_basalt_columns(data, area, minp, maxp) -- function from mapgen_mantle.lua
	end

	if contains_nether and contains_mantle then
		tunnelCandidate_count = tunnelCandidate_count + 1
		local success = mapgen.excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp) -- function from mapgen_mantle.lua
		if success then tunnel_count = tunnel_count + 1 end
	end
	total_chunk_count = total_chunk_count + 1
	if total_chunk_count % 50 == 0 then
		debugf(
			"%s of %s chunks contain both nether and lava-sea (%s%%), %s chunks generated a pathway (%s%%)",
			tunnelCandidate_count,
			total_chunk_count,
			math_floor(tunnelCandidate_count * 100 / total_chunk_count),
			tunnel_count,
			math_floor(tunnel_count * 100 / total_chunk_count)
		)
	end

	-- any air from the native mapgen has been replaced by netherrack, but we
	-- don't want netherrack inside dungeons, so fill known dungeon rooms with air.
	mapgen.excavate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua
	mapgen.decorate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua

	vm:set_data(data)

	minetest.generate_ores(vm)
	minetest.generate_decorations(vm)

	vm:set_lighting({day = 0, night = 0}, minp, maxp)
	vm:calc_lighting()
	vm:update_liquids()
	vm:write_to_map()
end

-- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal.
-- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
function nether.find_nether_ground_y(target_x, target_z, start_y, player_name)
	local nobj_cave_point = mapgen.getCavePointPerlin()
	local air = 0 -- Consecutive air nodes found

	local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, "nether_portal")
	local minp = {x = minp_schem.x, y = 0, z = minp_schem.z}
	local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z}

	for y = start_y, math_max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do
		local nval_cave = nobj_cave_point:get_3d({x = target_x, y = y, z = target_z})

		if nval_cave > TCAVE then -- Cavern
			air = air + 1
		else -- Not cavern, check if 4 nodes of space above
			if air >= 4 then
				local portal_y = y + 1
				-- Check volume for non-natural nodes
				minp.y = minp_schem.y + portal_y
				maxp.y = maxp_schem.y + portal_y
				if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
					return portal_y
				else -- Restart search a little lower
					nether.find_nether_ground_y(target_x, target_z, y - 16, player_name)
				end
			else -- Not enough space, reset air to zero
				air = 0
			end
		end
	end

	return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback
end

minetest.register_on_generated(on_generated)


code:
--[[

  Register decorations for Nether mapgen

  Copyright (C) 2020 Treer

  Permission to use, copy, modify, and/or distribute this software for
  any purpose with or without fee is hereby granted, provided that the
  above copyright notice and this permission notice appear in all copies.

  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
  BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
  OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  SOFTWARE.

]]--

-- Lava is unreliable in the old Nether mapgen because it removes lava
-- from the overdraw areas, so any decorations involving lava will often
-- have the lava missing depending on whether nearby chunks were already
-- emerged or not before the decoration was placed.
local allow_lava_decorations = nether.useBiomes

-- Keep compatibility with mapgen_nobiomes.lua, so hardcoding 128
-- instead of using nether.mapgen.BLEND
local decoration_ceiling = nether.DEPTH_CEILING - 128
local decoration_floor   = nether.DEPTH_FLOOR   + 128

local _  = {name = "air",                     prob = 0}
local A  = {name = "air",                     prob = 255, force_place = true}
local G  = {name = "nether:glowstone",        prob = 255, force_place = true}
local N  = {name = "nether:rack",             prob = 255}
local D  = {name = "nether:rack_deep",        prob = 255}
local S  = {name = "nether:sand",             prob = 255, force_place = true}
local L  = {name = "default:lava_source",     prob = 255, force_place = true}
local F  = {name = "nether:fumarole",         prob = 255, force_place = true}
local FS = {name = "nether:fumarole_slab",    prob = 255, force_place = true}
local F1 = {name = "nether:fumarole_corner",  prob = 255, force_place = true, param2 = 0}
local F2 = {name = "nether:fumarole_corner",  prob = 255, force_place = true, param2 = 1}
local F3 = {name = "nether:fumarole_corner",  prob = 255, force_place = true, param2 = 2}
local F4 = {name = "nether:fumarole_corner",  prob = 255, force_place = true, param2 = 3}
local S1 = {name = "stairs:stair_netherrack", prob = 255, force_place = true, param2 = 5}
local S2 = {name = "stairs:stair_netherrack", prob = 255, force_place = true, param2 = 7}
local S3 = {name = "stairs:stair_netherrack", prob = 255, force_place = true, param2 = 12}
local S4 = {name = "stairs:stair_netherrack", prob = 255, force_place = true, param2 = 16}

-- =================
--    Stalactites
-- =================

local schematic_GlowstoneStalactite = {
    size = {x = 5, y = 10, z = 5},
    data = { -- note that data is upside down
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, N, G, N, _,
        _, N, N, N, _,

        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, G, _, _,
        _, _, G, _, _,
        _, G, G, G, _,
        N, G, G, G, N,
        N, N, G, N, N,

        _, _, N, _, _,   -- ypos 0, prob 25% (64/256)
        _, _, G, _, _,   -- ypos 1, prob 37% (96/256)
        _, _, G, _, _,   -- ypos 2, prob 100%
        _, _, G, _, _,   -- ypos 3, prob 100%
        _, _, G, G, _,   -- ypos 4, prob 50% (128/256) to make half of stalactites asymmetric
        _, G, G, G, _,   -- ypos 5, prob 75% (192/256)
        _, G, G, G, _,   -- ypos 6, prob 75% (192/256)
        _, G, G, G, _,   -- ypos 7, prob 100%
        G, G, G, G, G,   -- ypos 8, prob 100%
        N, G, G, G, N,   -- ypos 9, prob 75% (192/256)

        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, G, _, _,
        _, _, G, _, _,
        _, _, G, _, _,
        _, G, G, G, _,
        N, G, G, G, N,
        N, N, G, N, N,

        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, _, _, _, _,
        _, N, G, N, _,
        _, N, N, N, _
    },
    -- Y-slice probabilities do not function correctly for ceiling schematic
    -- decorations because they are inverted, so ypos numbers have been inverted
    -- to match, and a larger offset in place_offset_y should be used (e.g. -3).
    yslice_prob = {
        {ypos = 9, prob = 192},
        {ypos = 6, prob = 192},
        {ypos = 5, prob = 192},
        {ypos = 4, prob = 128},
        {ypos = 1, prob = 96},
        {ypos = 0, prob = 64}
    }
}

minetest.register_decoration({
    name = "Glowstone stalactite",
    deco_type = "schematic",
    place_on = "nether:rack",
    sidelen = 80,
    fill_ratio = 0.0003,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = schematic_GlowstoneStalactite,
    flags = "place_center_x,place_center_z,force_placement,all_ceilings",
    place_offset_y=-3
})

minetest.register_decoration({
    name = "Netherrack stalactite",
    deco_type = "schematic",
    place_on = "nether:rack",
    sidelen = 80,
    fill_ratio = 0.0008,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = schematic_GlowstoneStalactite,
    replacements = {["nether:glowstone"] = "nether:rack"},
    flags = "place_center_x,place_center_z,all_ceilings",
    place_offset_y=-3
})

local schematic_GreaterStalactite = {
    size = {x = 3, y = 23, z = 3},
    data = { -- note that data is upside down

        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, D, _,
        _, D, _,
        _, D, _,
        _, D, _,
        D, D, D,
        D, D, D,
        D, D, D,
        _, D, _,
        _, _, _,
        _, _, _,

        _, D, _,  -- ypos 0, prob 85% (218/255)
        _, D, _,  -- ypos 1, prob 85% (218/255)
        _, D, _,  -- ypos 2, prob 85% (218/255)
        _, D, _,  -- ypos 3, prob 85% (218/255)
        _, D, _,  -- ypos 4, prob 85% (218/255)
        _, D, _,  -- ypos 5, prob 85% (218/255)
        _, D, _,  -- ypos 6, prob 85% (218/255)
        _, D, _,  -- ypos 7, prob 85% (218/255)
        _, D, _,  -- ypos 8, prob 85% (218/255)
        _, D, D,  -- ypos 9, prob 50% (128/256) to make half of stalactites asymmetric
        _, D, D,  -- ypos 10, prob 50% (128/256) to make half of stalactites asymmetric
        _, D, D,  -- ypos 11, prob 50% (128/256) to make half of stalactites asymmetric
        _, D, D,  -- ypos 12, prob 50% (128/256) to make half of stalactites asymmetric
        D, D, D,  -- ypos 13, prob 75% (192/256)
        D, D, D,  -- ypos 14, prob 75% (192/256)
        D, D, D,  -- ypos 15, prob 100%
        D, D, D,  -- ypos 16, prob 100%
        D, D, D,  -- ypos 17, prob 100%
        D, D, D,  -- ypos 18, prob 100%
        D, D, D,  -- ypos 19, prob 75% (192/256)
        D, D, D,  -- ypos 20, prob 85% (218/255)
        _, D, D,  -- ypos 21, prob 50% (128/256) to make half of stalactites asymmetric
        _, D, _,  -- ypos 22, prob 100%

        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, _, _,
        _, D, _,
        _, D, _,
        _, D, _,
        _, D, _,
        _, D, _,
        D, D, D,
        D, D, D,
        D, D, D,
        _, D, _,
        _, D, _,
        _, _, _,

    },
    -- Y-slice probabilities do not function correctly for ceiling schematic
    -- decorations because they are inverted, so ypos numbers have been inverted
    -- to match, and a larger offset in place_offset_y should be used (e.g. -3).
    yslice_prob = {
        {ypos = 21, prob = 128},
        {ypos = 20, prob = 218},
        {ypos = 19, prob = 192},
        {ypos = 14, prob = 192},
        {ypos = 13, prob = 192},
        {ypos = 12, prob = 128},
        {ypos = 11, prob = 128},
        {ypos = 10, prob = 128},
        {ypos =  9, prob = 128},
        {ypos =  8, prob = 218},
        {ypos =  7, prob = 218},
        {ypos =  6, prob = 218},
        {ypos =  5, prob = 218},
        {ypos =  4, prob = 218},
        {ypos =  3, prob = 218},
        {ypos =  2, prob = 218},
        {ypos =  1, prob = 218},
        {ypos =  0, prob = 218}
    }
}

-- A stalagmite is an upsidedown stalactite, so
-- use the GreaterStalactite to create a ToweringStalagmite schematic
local schematic_ToweringStalagmite = {
    size = schematic_GreaterStalactite.size,
    data = {},
    yslice_prob = {}
}
local array_length = #schematic_GreaterStalactite.data + 1
for i, node in ipairs(schematic_GreaterStalactite.data) do
    schematic_ToweringStalagmite.data[array_length - i] = node
end
y_size = schematic_GreaterStalactite.size.y
for i, node in ipairs(schematic_GreaterStalactite.yslice_prob) do
    schematic_ToweringStalagmite.yslice_prob[i] = {
        -- we can safely lower the prob. to gain more variance because floor based schematics
        -- don't have the bug where missing lines moves them away from the surface
        prob = schematic_GreaterStalactite.yslice_prob[i].prob - 20,
        ypos = y_size - 1 - schematic_GreaterStalactite.yslice_prob[i].ypos
    }
end

minetest.register_decoration({
    name = "Deep-glowstone stalactite",
    deco_type = "schematic",
    place_on = "nether:rack_deep",
    sidelen = 80,
    fill_ratio = 0.0003,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = schematic_GlowstoneStalactite,
    replacements = {["nether:rack"] = "nether:rack_deep", ["nether:glowstone"] = "nether:glowstone_deep"},
    flags = "place_center_x,place_center_z,force_placement,all_ceilings",
    place_offset_y=-3
})

minetest.register_decoration({
    name = "Deep-glowstone stalactite outgrowth",
    deco_type = "schematic",
    place_on = "nether:glowstone_deep",
    sidelen = 40,
    fill_ratio = 0.15,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = {
        size = {x = 1, y = 4, z = 1},
        data = { G, G, G, G }
    },
    replacements = {["nether:glowstone"] = "nether:glowstone_deep"},
    flags = "place_center_x,place_center_z,all_ceilings",
})

minetest.register_decoration({
    name = "Deep-netherrack stalactite",
    deco_type = "schematic",
    place_on = "nether:rack_deep",
    sidelen = 80,
    fill_ratio = 0.0003,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = schematic_GlowstoneStalactite,
    replacements = {["nether:rack"] = "nether:rack_deep", ["nether:glowstone"] = "nether:rack_deep"},
    flags = "place_center_x,place_center_z,force_placement,all_ceilings",
    place_offset_y=-3
})

minetest.register_decoration({
    name = "Deep-netherrack towering stalagmite",
    deco_type = "schematic",
    place_on = "nether:rack_deep",
    sidelen = 80,
    fill_ratio = 0.001,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = schematic_ToweringStalagmite,
    replacements = {["nether:basalt"] = "nether:rack_deep"},
    flags = "place_center_x,place_center_z,force_placement,all_floors",
    place_offset_y=-2
})

-- =======================================
--    Concealed crevice / Lava sinkhole
-- =======================================
-- if player places a torch/block on this sand or digs it while standing on it, it sinks into lava

if allow_lava_decorations then
    minetest.register_decoration({
        name = "Weak trap",
        deco_type = "schematic",
        place_on = "nether:rack",
        sidelen = 80,
        fill_ratio = 0.002,
        biomes = {"nether_caverns"},
        y_max = decoration_ceiling,
        y_min = decoration_floor,
        schematic = {
            size = {x = 4, y = 7, z = 4},
            data = { -- note that data is upside down
                _, _, _, _,
                _, _, _, _,
                _, N, _, _,
                _, N, N, _,
                _, N, N, _,
                _, N, N, _,
                _, _, _, _,

                _, N, _, _, -- make it look like a stalactite if it protrudes out the bottom of a landform
                _, N, _, _,
                N, L, N, _,
                N, L, L, N,
                N, L, L, N,
                N, A, A, N,
                _, S, S, _,

                _, _, _, _,
                _, _, _, _,
                _, N, N, _,
                N, L, L, N,
                N, L, L, N,
                N, A, A, N,
                _, S, S, _,

                _, _, _, _,
                _, _, _, _,
                _, _, _, _,
                _, N, N, _,
                _, N, N, _,
                _, N, N, _,
                _, _, _, _,
            }
        },
        replacements = {["nether:glowstone"] = "nether:rack"},
        flags = "place_center_x,place_center_z,force_placement, all_floors",
        place_offset_y=-6,
        rotation = "random"
    })
end

-- ==========================
--    Fumaroles (Chimneys)
-- ==========================

local replacements_slab = {}
local replacements_full = {["nether:fumarole_slab"] = "nether:fumarole"}

if allow_lava_decorations then
    -- Minetest engine limitations mean any mesh or nodebox node (like nether:fumarole)
    -- will light up if it has lava below it, so replace the air node over the lava with
    -- a node that prevents light propagation.
    -- (Unfortunately this also means if a player digs down to get the lava block it'll
    -- look like the lighting wasn't set in the block above the lava)
    replacements_slab["air"] = "nether:airlike_darkness"
    replacements_full["air"] = "nether:airlike_darkness"
else
    -- Lava is frequently removed by the old mapgen, so put sand at the bottom
    -- of fumaroles.
    replacements_slab["default:lava_source"] = "nether:sand"
    replacements_full["default:lava_source"] = "nether:sand"
end

local schematic_fumarole = {
    size = {x = 3, y = 5, z = 3},
    data = { -- note that data is upside down
        _, _, _,
        _, N, _,
        _, N, _,
        _, _, _,
        _, _, _,

        _, N, _,
        N, L, N,
        N, A, N,
        _, F, _,
        _,FS, _,

        _, _, _,
        _, N, _,
        _, N, _,
        _, _, _,
        _, _, _,
    },
}

-- Common fumarole decoration that's flush with the floor and spawns everywhere

minetest.register_decoration({
    name = "Sunken nether fumarole",
    deco_type = "schematic",
    place_on = {"nether:rack"},
    sidelen = 80,
    fill_ratio = 0.005,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = schematic_fumarole,
    replacements = replacements_full,
    flags = "place_center_x,place_center_z,all_floors",
    place_offset_y=-4
})

-- Rarer formations of raised fumaroles in clumps

local fumarole_clump_noise_offset = -0.58
local fumarole_clump_noise = {
    offset = fumarole_clump_noise_offset,
    scale = 0.5,
    spread = {x = 40, y = 40, z = 15},
    octaves = 4,
    persist = 0.65,
    lacunarity = 2.0,
}

fumarole_clump_noise.offset = fumarole_clump_noise_offset - 0.035
minetest.register_decoration({
    name = "Raised Nether fumarole",
    deco_type = "schematic",
    place_on = {"nether:rack"},
    sidelen = 8,
    noise_params = fumarole_clump_noise,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = schematic_fumarole,
    replacements = replacements_full,
    flags = "place_center_x,place_center_z,all_floors",
    place_offset_y=-3
})

fumarole_clump_noise.offset = fumarole_clump_noise_offset
minetest.register_decoration({
    name = "Half-raised Nether fumarole",
    deco_type = "schematic",
    place_on = {"nether:rack"},
    sidelen = 8,
    noise_params = fumarole_clump_noise,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = schematic_fumarole,
    replacements = replacements_slab,
    flags = "place_center_x,place_center_z,all_floors",
    place_offset_y=-3
})

fumarole_clump_noise.offset = fumarole_clump_noise_offset - 0.035
minetest.register_decoration({
    name = "Nether fumarole mound",
    deco_type = "schematic",
    place_on = {"nether:rack"},
    sidelen = 8,
    noise_params = fumarole_clump_noise,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = {
        size = {x = 4, y = 4, z = 4},
        data = { -- note that data is upside down
            _, _,  _,  _,
            _, N,  N,  _,
            _, _,  _,  _,
            _, _,  _,  _,

            _, S,  S,  _,
            N, A,  A,  N,
            _, S2, S1, _,
            _, F2, F1, _,

            _, S,  S,  _,
            N, A,  A,  N,
            _, S3, S4, _,
            _, F3, F4, _,

            _, _,  _,  _,
            _, N,  N,  _,
            _, _,  _,  _,
            _, _,  _,  _
        },
        yslice_prob = {{ypos = 3, prob = 192}} -- occasionally leave the fumarole cap off
    },
    flags = "place_center_x,place_center_z,all_floors",
    place_offset_y = -2
})

fumarole_clump_noise.offset = fumarole_clump_noise_offset - 0.01
minetest.register_decoration({
    name = "Double Nether fumarole",
    deco_type = "schematic",
    place_on = {"nether:rack"},
    sidelen = 8,
    noise_params = fumarole_clump_noise,
    biomes = {"nether_caverns"},
    y_max = decoration_ceiling,
    y_min = decoration_floor,
    schematic = {
        size = {x = 4, y = 5, z = 4},
        data = { -- note that data is upside down
            _, _,  _,  _,
            _, N,  N,  _,
            _, _,  _,  _,
            _, _,  _,  _,
            _, _,  _,  _,

            _, S,  S,  _,
            N, A,  A,  N,
            _, S2, S1, _,
            _, F2, F,  _,
            _, _,  FS, _,

            _, S,  S,  _,
            F, A,  A,  N, -- the F may add slight variance in landforms where it gets exposed
            _, S3, S4, _,
            _, F3, F4, _,
            _, _,  _,  _,

            _, _,  _,  _,
            _, N,  N,  _,
            _, _,  _,  _,
            _, _,  _,  _,
            _, _,  _,  _
        }
    },
    flags = "place_center_x,place_center_z,all_floors",
    place_offset_y = -2,
    rotation = "random"
})


code:
--[[

  Nether mod for minetest

  All the Dungeon related functions used by the biomes-based mapgen are here.

  Copyright (C) 2021 Treer

  Permission to use, copy, modify, and/or distribute this software for
  any purpose with or without fee is hereby granted, provided that the
  above copyright notice and this permission notice appear in all copies.

  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
  BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
  OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  SOFTWARE.

]]--

-- We don't need to be gen-notified of temples because only dungeons will be generated
-- if a biome defines the dungeon nodes
minetest.set_gen_notify({dungeon = true})

-- Content ids

local c_air              = minetest.get_content_id("air")
local c_netherrack       = minetest.get_content_id("nether:rack")
local c_netherrack_deep  = minetest.get_content_id("nether:rack_deep")
local c_dungeonbrick     = minetest.get_content_id("nether:brick")
local c_dungeonbrick_alt = minetest.get_content_id("nether:brick_cracked")
local c_netherbrick_slab = minetest.get_content_id("stairs:slab_nether_brick")
local c_netherfence      = minetest.get_content_id("nether:fence_nether_brick")
local c_glowstone        = minetest.get_content_id("nether:glowstone")
local c_glowstone_deep   = minetest.get_content_id("nether:glowstone_deep")
local c_lava_source      = minetest.get_content_id("default:lava_source")

-- Misc math functions

-- avoid needing table lookups each time a common math function is invoked
local math_max, math_min  = math.max, math.min

-- Dungeon excavation functions

function is_dungeon_brick(node_id)
	return node_id == c_dungeonbrick or node_id == c_dungeonbrick_alt
end

nether.mapgen.build_dungeon_room_list = function(data, area)

	local result = {}

	-- Unfortunately gennotify only returns dungeon rooms, not corridors.
	-- We don't need to check for temples because only dungeons are generated in biomes
	-- that define their own dungeon nodes.
	local gennotify = minetest.get_mapgen_object("gennotify")
	local roomLocations = gennotify["dungeon"] or {}

	-- Excavation should still know to stop if a cave or corridor has removed the dungeon wall.
	-- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes.
	local maxRoomSize = 18
	local maxRoomRadius = math.ceil(maxRoomSize / 2)

	local xStride, yStride, zStride = 1, area.ystride, area.zstride
	local minEdge, maxEdge = area.MinEdge, area.MaxEdge

	for _, roomPos in ipairs(roomLocations) do

		if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit

			local room_vi = area:indexp(roomPos)
			--data[room_vi] = minetest.get_content_id("default:torch") -- debug

			local startPos = vector.new(roomPos)
			if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then
				-- The roomPos coords given by gennotify are at floor level, but whenever possible we
				-- want to be performing searches a node higher than floor level to avoids dungeon chests.
				startPos.y = startPos.y + 1
				room_vi = area:indexp(startPos)
			end

			local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius)
			local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor
			local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius)

			local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius)
			local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor
			local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius)

			local room_min = vector.new(startPos)
			local room_max = vector.new(startPos)

			local vi = room_vi
			while room_max.y < bound_max_y and data[vi + yStride] == c_air do
				room_max.y = room_max.y + 1
				vi = vi + yStride
			end

			vi = room_vi
			while room_min.y > bound_min_y and data[vi - yStride] == c_air do
				room_min.y = room_min.y - 1
				vi = vi - yStride
			end

			vi = room_vi
			while room_max.z < bound_max_z and data[vi + zStride] == c_air do
				room_max.z = room_max.z + 1
				vi = vi + zStride
			end

			vi = room_vi
			while room_min.z > bound_min_z and data[vi - zStride] == c_air do
				room_min.z = room_min.z - 1
				vi = vi - zStride
			end

			vi = room_vi
			while room_max.x < bound_max_x and data[vi + xStride] == c_air do
				room_max.x = room_max.x + 1
				vi = vi + xStride
			end

			vi = room_vi
			while room_min.x > bound_min_x and data[vi - xStride] == c_air do
				room_min.x = room_min.x - 1
				vi = vi - xStride
			end

			local roomInfo = vector.new(roomPos)
			roomInfo.minp = room_min
			roomInfo.maxp = room_max
			result[#result + 1] = roomInfo
		end
	end

	return result;
end

-- Only partially excavates dungeons, the rest is left as an exercise for the player ;)
-- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled)
nether.mapgen.excavate_dungeons = function(data, area, rooms)

	local vi, node_id

	-- any air from the native mapgen has been replaced by netherrack, but
	-- we don't want this inside dungeons, so fill dungeon rooms with air
	for _, roomInfo in ipairs(rooms) do

		local room_min = roomInfo.minp
		local room_max = roomInfo.maxp

		for z = room_min.z, room_max.z do
			for y = room_min.y, room_max.y do
				vi = area:index(room_min.x, y, z)
				for x = room_min.x, room_max.x do
					node_id = data[vi]
					if node_id == c_netherrack or node_id == c_netherrack_deep then data[vi] = c_air end
					vi = vi + 1
				end
			end
		end
	end

	-- clear netherrack from dungeon stairways
	if #rooms > 0 then
		local stairPositions = minetest.find_nodes_in_area(area.MinEdge, area.MaxEdge, minetest.registered_biomes["nether_caverns"].node_dungeon_stair)
		for _, stairPos in ipairs(stairPositions) do
			vi = area:indexp(stairPos)
			for i = 1, 4 do
				if stairPos.y + i > area.MaxEdge.y then break end
				vi = vi + area.ystride
				node_id = data[vi]
				-- searching forward of the stairs could also be done
				if node_id == c_netherrack or node_id == c_netherrack_deep then data[vi] = c_air end
			end
		end
	end
end

-- Since we already know where all the rooms and their walls are, and have all the nodes stored
-- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here.
nether.mapgen.decorate_dungeons = function(data, area, rooms)

	local xStride, yStride, zStride = 1, area.ystride, area.zstride
	local minEdge, maxEdge = area.MinEdge, area.MaxEdge

	for _, roomInfo in ipairs(rooms) do

		local room_min, room_max = roomInfo.minp, roomInfo.maxp
		local room_size = vector.distance(room_min, room_max)

		if room_size > 10 then
			local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y
			local window_y  = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1)
			local roomWidth  = room_max.x - room_min.x + 1
			local roomLength = room_max.z - room_min.z + 1

			if room_seed % 3 == 0 and room_max.y < maxEdge.y then
				-- Glowstone chandelier (feel free to replace with a fancy schematic)
				local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z)
				if is_dungeon_brick(data[vi]) then data[vi] = c_glowstone end

			elseif room_seed % 4 == 0 and room_min.y > minEdge.y
				   and room_min.x > minEdge.x and room_max.x < maxEdge.x
				   and room_min.z > minEdge.z and room_max.z < maxEdge.z then
				-- lava well (feel free to replace with a fancy schematic)
				local vi = area:index(roomInfo.x, room_min.y, roomInfo.z)
				if is_dungeon_brick(data[vi - yStride]) then
					data[vi - yStride] = c_lava_source
					if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end
					if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end
					if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end
					if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end
				end
			end

			-- Barred windows
			if room_seed % 7 < 5 and roomWidth >= 5 and roomLength >= 5
			   and window_y >= minEdge.y and window_y + 1 <= maxEdge.y
			   and room_min.x > minEdge.x and room_max.x < maxEdge.x
			   and room_min.z > minEdge.z and room_max.z < maxEdge.z then
				--data[area:indexp(roomInfo)] = minetest.get_content_id("default:mese_post_light") -- debug

				-- Can't use glass panes because they need the param data set.
				-- Until whisper glass is added, every window will be made of netherbrick fence (rather
				-- than material depending on room_seed)
				local window_node = c_netherfence
				--if c_netherglass ~= nil and room_seed % 20 >= 12 then window_node = c_crystallight end

				local function placeWindow(vi, viOutsideOffset, windowNo)
					if is_dungeon_brick(data[vi]) and is_dungeon_brick(data[vi + yStride]) then
						data[vi] = window_node

						if room_seed % 19 == windowNo then
							-- place a glowstone light behind the window
							local node_id = data[vi + viOutsideOffset]
							if node_id == c_netherrack then
								data[vi + viOutsideOffset] = c_glowstone
							elseif node_id == c_netherrack_deep then
								data[vi + viOutsideOffset] = c_glowstone_deep
							end
						end
					end
				end

				local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z)
				local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z)
				local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride}
				for i, offset in ipairs(locations) do
					placeWindow(vi_min + offset, -1, i)
					placeWindow(vi_max + offset,  1, i + #locations)
				end
				vi_min = area:index(roomInfo.x, window_y, room_min.z - 1)
				vi_max = area:index(roomInfo.x, window_y, room_max.z + 1)
				locations = {-xStride, xStride, -xStride + yStride, xStride + yStride}
				for i, offset in ipairs(locations) do
					placeWindow(vi_min + offset, -zStride, i + #locations * 2)
					placeWindow(vi_max + offset,  zStride, i + #locations * 3)
				end
			end

			-- pillars or mezzanine floor
			if room_seed % 43 > 10 and roomWidth >= 6 and roomLength >= 6 then

				local pillar_vi    = {}
				local pillarHeight = 0
				local wallDist = 1 + math.floor((roomWidth + roomLength) / 14)

				local roomHeight = room_max.y - room_min.y
				if roomHeight >= 7 then
					-- mezzanine floor
					local mezzMax = {
						x = room_min.x + math.floor(roomWidth / 7 * 4),
						y = room_min.y + math.floor(roomHeight / 5 * 3),
						z = room_max.z
					}

					pillarHeight = mezzMax.y - room_min.y - 1
					pillar_vi = {
						area:index(mezzMax.x, room_min.y, room_min.z + wallDist),
						area:index(mezzMax.x, room_min.y, room_max.z - wallDist),
					}

					if is_dungeon_brick(data[pillar_vi[1] - yStride]) and is_dungeon_brick(data[pillar_vi[2] - yStride]) then
						-- The floor of the dungeon looks like it exists (i.e. not erased by nether
						-- cavern), so add the mezzanine floor
						for z = 0, roomLength - 1 do
							local vi = area:index(room_min.x, mezzMax.y, room_min.z + z)
							for x = room_min.x, mezzMax.x do
								if data[vi] == c_air then data[vi] = c_dungeonbrick end
								vi = vi + 1
							end
						end
					end

				elseif roomHeight >= 4 then
					-- 4 pillars
					pillarHeight = roomHeight
					pillar_vi = {
						area:index(room_min.x + wallDist, room_min.y, room_min.z + wallDist),
						area:index(room_min.x + wallDist, room_min.y, room_max.z - wallDist),
						area:index(room_max.x - wallDist, room_min.y, room_min.z + wallDist),
						area:index(room_max.x - wallDist, room_min.y, room_max.z - wallDist)
					}
				end

				for i = #pillar_vi, 1, -1 do
					if not is_dungeon_brick(data[pillar_vi[i] - yStride]) then
						-- there's no dungeon floor under this pillar so skip it, it's probably been cut away by nether cavern.
						table.remove(pillar_vi, i)
					end
				end
				for y = 0, pillarHeight do
					for _, vi in ipairs(pillar_vi) do
						if data[vi + y * yStride] == c_air then data[vi + y * yStride] = c_dungeonbrick end
					end
				end
			end

			-- Weeds on the floor once Nether weeds are added
		end
	end
end