From 64907ead0d7449a192d7a21d8f91478715e1f854 Mon Sep 17 00:00:00 2001
From: mirqf <nitrotwink@mail.ru>
Date: Fri, 24 Nov 2023 11:21:59 +0100
Subject: [PATCH 1/5] Add cartography table functionality

Co-authored-by: AFCMS <afcm.contact@gmail.com>
---
 mods/ITEMS/mcl_cartography_table/README.md |   2 +-
 mods/ITEMS/mcl_cartography_table/init.lua  | 144 ++++++++++++++++++++-
 mods/ITEMS/mcl_cartography_table/mod.conf  |   4 +-
 3 files changed, 142 insertions(+), 8 deletions(-)

diff --git a/mods/ITEMS/mcl_cartography_table/README.md b/mods/ITEMS/mcl_cartography_table/README.md
index 0d66c8042..21e3fe55c 100644
--- a/mods/ITEMS/mcl_cartography_table/README.md
+++ b/mods/ITEMS/mcl_cartography_table/README.md
@@ -1,6 +1,6 @@
 mcl_cartography_table
 -------------------
-Cartography Tables, by PrairieWind
+Cartography Tables, by PrairieWind, mirqf and AFCM
 
 Adds Cartography Tables to VoxeLibre.
 
diff --git a/mods/ITEMS/mcl_cartography_table/init.lua b/mods/ITEMS/mcl_cartography_table/init.lua
index 269a49ede..4d078475d 100644
--- a/mods/ITEMS/mcl_cartography_table/init.lua
+++ b/mods/ITEMS/mcl_cartography_table/init.lua
@@ -1,10 +1,94 @@
 local S = minetest.get_translator(minetest.get_current_modname())
--- Cartography Table Code. Used to create and copy maps. TODO: Needs a GUI still.
+local C = minetest.colorize
+local F = minetest.formspec_escape
+
+local function refresh_cartography(pos, player)
+	local formspec = table.concat({
+		"formspec_version[4]",
+		"size[11.75,10.425]",
+		"label[0.375,0.375;" .. F(C(mcl_formspec.label_color, S("Cartography Table"))) .. "]",
+
+		-- First input slot
+		mcl_formspec.get_itemslot_bg_v4(1, 0.75, 1, 1),
+		"list[nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ";input;1,0.75;1,1;1]",
+
+		-- Cross icon
+		"image[1,2;1,1;mcl_anvils_inventory_cross.png]",
+
+		-- Second input slot
+		mcl_formspec.get_itemslot_bg_v4(1, 3.25, 1, 1),
+		"list[nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ";input;1,3.25;1,1;]",
+
+		-- Arrow
+		"image[2.7,2;2,1;mcl_anvils_inventory_arrow.png]",
+
+		-- Output slot
+		mcl_formspec.get_itemslot_bg_v4(9.75, 2, 1, 1, 0.2),
+		"list[nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ";output;9.75,2;1,1;]",
+
+		-- Player inventory
+		"label[0.375,4.7;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]",
+		mcl_formspec.get_itemslot_bg_v4(0.375, 5.1, 9, 3),
+		"list[current_player;main;0.375,5.1;9,3;9]",
+
+		mcl_formspec.get_itemslot_bg_v4(0.375, 9.05, 9, 1),
+		"list[current_player;main;0.375,9.05;9,1;]",
+	})
+
+	local inv = minetest.get_meta(pos):get_inventory()
+	local map = inv:get_stack("input", 2)
+	local texture = mcl_maps.load_map_item(map)
+	local marker = inv:get_stack("input", 1):get_name()
+
+	if marker == "mcl_maps:empty_map" then
+		if texture then
+			formspec = formspec .. table.concat({
+				"image[6.125,0.5;3,3;mcl_maps_map_background.png]",
+				"image[6.375,0.75;2.5,2.5;" .. texture .. "]",
+				"image[5.125,1.5;3,3;mcl_maps_map_background.png]",
+				"image[5.375,1.75;2.5,2.5;" .. texture .. "]"
+			})
+		else
+			formspec = formspec .. table.concat({
+				"image[6.125,0.5;3,3;mcl_maps_map_background.png]",
+				"image[5.125,1.5;3,3;mcl_maps_map_background.png]"
+			})
+		end
+		if not map:is_empty() then
+			map:set_count(2)
+			inv:set_stack("output", 1, map)
+		end
+	else
+		formspec = formspec .. "image[5.125,0.5;4,4;mcl_maps_map_background.png]"
+		--formspec = formspec .. "box[5.125,0.5;4,4;#FFFFFF]"
+		if texture then formspec = formspec .. "image[5.375,0.75;3.5,3.5;" .. texture .. "]" end
+		if marker == "xpanes:pane_natural_flat" and not map:is_empty() then
+			if map:get_meta():get_int("locked") == 1 then
+				formspec = formspec .. table.concat({
+					"image[3.2,2;1,1;mcl_core_barrier.png]",
+					"image[8.375,3.75;0.5,0.5;mcl_core_barrier.png]"
+				})
+			else
+				formspec = formspec .. "image[8.375,3.75;0.5,0.5;mcl_core_barrier.png]"
+				map:get_meta():set_int("locked", 1)
+				inv:set_stack("output", 1, map)
+			end
+		end
+	end
+
+	minetest.show_formspec(player:get_player_name(), "mcl_cartography_table", formspec)
+end
+
+local allowed_to_put = {
+	--["mcl_core:paper"] = true, Requires missing features with increasing map size
+	["mcl_maps:empty_map"] = true,
+	["xpanes:pane_natural_flat"] = true
+}
 
 minetest.register_node("mcl_cartography_table:cartography_table", {
 	description = S("Cartography Table"),
 	_tt_help = S("Used to create or copy maps"),
-	_doc_items_longdesc = S("Is used to create or copy maps for use.."),
+	_doc_items_longdesc = S("Is used to create or copy maps for use."),
 	tiles = {
 		"mcl_cartography_table_top.png", "mcl_cartography_table_side3.png",
 		"mcl_cartography_table_side3.png", "mcl_cartography_table_side2.png",
@@ -14,9 +98,59 @@ minetest.register_node("mcl_cartography_table:cartography_table", {
 	groups = { axey = 2, handy = 1, deco_block = 1, material_wood = 1, flammable = 1 },
 	sounds = mcl_sounds.node_sound_wood_defaults(),
 	_mcl_blast_resistance = 2.5,
-	_mcl_hardness = 2.5
-	})
-
+	_mcl_hardness = 2.5,
+	on_construct = function(pos)
+		local inv = minetest.get_meta(pos):get_inventory()
+		inv:set_size("input", 2)
+		inv:set_size("output", 1)
+	end,
+	allow_metadata_inventory_put = function(pos, listname, index, stack, player)
+		if minetest.is_protected(pos, player:get_player_name()) or listname == "output" then
+			return 0
+		else
+			if index == 2 and not stack:get_name():find("filled_map") then return 0 end
+			if index == 1 and not allowed_to_put[stack:get_name()] then return 0 end
+			return stack:get_count()
+		end
+	end,
+	on_metadata_inventory_put = function(pos, _, _, _, player)
+		refresh_cartography(pos, player)
+	end,
+	on_metadata_inventory_take = function(pos, listname, _, _, player)
+		local inv = minetest.get_meta(pos):get_inventory()
+		if listname == "output" then
+			local first = inv:get_stack("input", 2); first:take_item(); inv:set_stack("input", 2, first)
+			local second = inv:get_stack("input", 1); second:take_item(); inv:set_stack("input", 1, second)
+		else
+			inv:set_stack("output", 1, "")
+		end
+		refresh_cartography(pos, player)
+	end,
+	allow_metadata_inventory_move = function() return 0 end,
+	allow_metadata_inventory_take = function(pos, listname, index, stack, player)
+		return 0 and minetest.is_protected(pos, player:get_player_name()) or stack:get_count()
+	end,
+	on_rightclick = function(pos, node, player, itemstack)
+		if not player:get_player_control().sneak then refresh_cartography(pos, player) end
+	end,
+	after_dig_node = function(pos, _, oldmetadata, _)
+		local meta = minetest.get_meta(pos)
+		local meta2 = meta:to_table()
+		meta:from_table(oldmetadata)
+		local inv = meta:get_inventory()
+		for i = 1, inv:get_size("input") do
+			local stack = inv:get_stack("input", i)
+			if not stack:is_empty() then
+				minetest.add_item(vector.offset(pos,
+					math.random(0, 10) / 10 - 0.5,
+					0,
+					math.random(0, 10) / 10 - 0.5
+				), stack)
+			end
+		end
+		meta:from_table(meta2)
+	end,
+})
 
 minetest.register_craft({
 	output = "mcl_cartography_table:cartography_table",
diff --git a/mods/ITEMS/mcl_cartography_table/mod.conf b/mods/ITEMS/mcl_cartography_table/mod.conf
index 0a80c0111..5b57c0b6b 100644
--- a/mods/ITEMS/mcl_cartography_table/mod.conf
+++ b/mods/ITEMS/mcl_cartography_table/mod.conf
@@ -1,4 +1,4 @@
 name = mcl_cartography_table
-author = PrairieWind
-depends = mcl_core, mcl_sounds, mcl_tools
+author = PrairieWind, mirqf, AFCM
+depends = mcl_core, mcl_sounds, mcl_tools, mcl_formspec
 description = Adds the cartography table villager workstation to MineClone 2/5. Used to copy and create maps.
\ No newline at end of file

From 88ca202647c9cee02a09e87bfcf1e5f91c9d0415 Mon Sep 17 00:00:00 2001
From: kno10 <erich.schubert@gmail.com>
Date: Sat, 7 Dec 2024 13:33:15 +0100
Subject: [PATCH 2/5] Zoomable maps, but limited to 1 level for now.

---
 mods/ITEMS/mcl_cartography_table/init.lua | 240 +++++++-----
 mods/ITEMS/mcl_cartography_table/mod.conf |   6 +-
 mods/ITEMS/mcl_maps/init.lua              | 421 +++++++++++++---------
 mods/ITEMS/mcl_maps/mod.conf              |   3 +-
 settingtypes.txt                          |   6 +
 5 files changed, 417 insertions(+), 259 deletions(-)

diff --git a/mods/ITEMS/mcl_cartography_table/init.lua b/mods/ITEMS/mcl_cartography_table/init.lua
index 4d078475d..3cc2f6fb5 100644
--- a/mods/ITEMS/mcl_cartography_table/init.lua
+++ b/mods/ITEMS/mcl_cartography_table/init.lua
@@ -1,8 +1,18 @@
-local S = minetest.get_translator(minetest.get_current_modname())
-local C = minetest.colorize
-local F = minetest.formspec_escape
+local S = core.get_translator(core.get_current_modname())
+local C = core.colorize
+local F = core.formspec_escape
+
+local formspec_name = "mcl_cartography_table:cartography_table"
+
+-- Crafting patterns supported:
+-- 1. Filled map + paper = zoomed out map, but only ONCE for now (too slow)
+-- 2. Filled map + empty map = two copies of the map
+-- 3. Filled map + glass pane = locked filled map
+-- TODO: allow refreshing a map using the table?
+
+local function update_cartography_table(player)
+	if not player or not player:is_player() then return end
 
-local function refresh_cartography(pos, player)
 	local formspec = table.concat({
 		"formspec_version[4]",
 		"size[11.75,10.425]",
@@ -10,21 +20,21 @@ local function refresh_cartography(pos, player)
 
 		-- First input slot
 		mcl_formspec.get_itemslot_bg_v4(1, 0.75, 1, 1),
-		"list[nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ";input;1,0.75;1,1;1]",
+		"list[current_player;cartography_table_input;1,0.75;1,1;0]",
 
 		-- Cross icon
 		"image[1,2;1,1;mcl_anvils_inventory_cross.png]",
 
 		-- Second input slot
 		mcl_formspec.get_itemslot_bg_v4(1, 3.25, 1, 1),
-		"list[nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ";input;1,3.25;1,1;]",
+		"list[current_player;cartography_table_input;1,3.25;1,1;1]",
 
 		-- Arrow
 		"image[2.7,2;2,1;mcl_anvils_inventory_arrow.png]",
 
 		-- Output slot
 		mcl_formspec.get_itemslot_bg_v4(9.75, 2, 1, 1, 0.2),
-		"list[nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ";output;9.75,2;1,1;]",
+		"list[current_player;cartography_table_output;9.75,2;1,1;]",
 
 		-- Player inventory
 		"label[0.375,4.7;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]",
@@ -35,12 +45,27 @@ local function refresh_cartography(pos, player)
 		"list[current_player;main;0.375,9.05;9,1;]",
 	})
 
-	local inv = minetest.get_meta(pos):get_inventory()
-	local map = inv:get_stack("input", 2)
-	local texture = mcl_maps.load_map_item(map)
-	local marker = inv:get_stack("input", 1):get_name()
+	local inv = player:get_inventory()
+	local map = inv:get_stack("cartography_table_input", 1)
+	local texture = not map:is_empty() and mcl_maps.load_map_item(map)
+	local addon = inv:get_stack("cartography_table_input", 2)
+	inv:set_stack("cartography_table_output", 1, nil)
 
-	if marker == "mcl_maps:empty_map" then
+	if not map:is_empty() and addon:get_name() == "mcl_core:paper"
+			and map:get_meta():get_int("mcl_maps:zoom") < mcl_maps.max_zoom
+			and map:get_meta():get_int("mcl_maps:locked") ~= 1 then
+		---- Zoom a map
+		formspec = formspec .. "image[5.125,0.5;4,4;mcl_maps_map_background.png]"
+		-- TODO: show half size in appropriate position?
+		if texture then formspec = formspec .. "image[6.25,1.625;1.75,1.75;" .. texture .. "]" end
+		-- zoom will be really applied when taking from the stack
+		-- to not cause unnecessary map generation. But the tooltip should be right already:
+		map:get_meta():set_int("mcl_maps:zoom", map:get_meta():get_int("mcl_maps:zoom") + 1)
+		tt.reload_itemstack_description(map)
+		inv:set_stack("cartography_table_output", 1, map)
+
+	elseif not map:is_empty() and addon:get_name() == "mcl_maps:empty_map" then
+		---- Copy a map
 		if texture then
 			formspec = formspec .. table.concat({
 				"image[6.125,0.5;3,3;mcl_maps_map_background.png]",
@@ -54,41 +79,128 @@ local function refresh_cartography(pos, player)
 				"image[5.125,1.5;3,3;mcl_maps_map_background.png]"
 			})
 		end
-		if not map:is_empty() then
-			map:set_count(2)
-			inv:set_stack("output", 1, map)
+		map:set_count(2)
+		inv:set_stack("cartography_table_output", 1, map)
+
+	elseif addon:get_name() == "xpanes:pane_natural_flat" and not map:is_empty() then
+		---- Lock a map
+		formspec = formspec .. "image[5.125,0.5;4,4;mcl_maps_map_background.png]"
+		if texture then formspec = formspec .. "image[5.375,0.75;3.5,3.5;" .. texture .. "]" end
+		if map:get_meta():get_int("mcl_maps:locked") == 1 then
+			formspec = formspec .. table.concat({
+				"image[3.2,2;1,1;mcl_core_barrier.png]",
+				"image[8.375,3.75;0.5,0.5;mcl_core_barrier.png]"
+			})
+		else
+			formspec = formspec .. "image[8.375,3.75;0.5,0.5;mcl_core_barrier.png]"
+			map:get_meta():set_int("mcl_maps:locked", 1)
+			inv:set_stack("cartography_table_output", 1, map)
 		end
 	else
+		---- Not supported
 		formspec = formspec .. "image[5.125,0.5;4,4;mcl_maps_map_background.png]"
-		--formspec = formspec .. "box[5.125,0.5;4,4;#FFFFFF]"
 		if texture then formspec = formspec .. "image[5.375,0.75;3.5,3.5;" .. texture .. "]" end
-		if marker == "xpanes:pane_natural_flat" and not map:is_empty() then
-			if map:get_meta():get_int("locked") == 1 then
-				formspec = formspec .. table.concat({
-					"image[3.2,2;1,1;mcl_core_barrier.png]",
-					"image[8.375,3.75;0.5,0.5;mcl_core_barrier.png]"
-				})
-			else
-				formspec = formspec .. "image[8.375,3.75;0.5,0.5;mcl_core_barrier.png]"
-				map:get_meta():set_int("locked", 1)
-				inv:set_stack("output", 1, map)
-			end
+	end
+
+	core.show_formspec(player:get_player_name(), formspec_name, formspec)
+end
+
+core.register_on_joinplayer(function(player)
+	local inv = player:get_inventory()
+
+	inv:set_size("cartography_table_input", 2)
+	inv:set_size("cartography_table_output", 1)
+
+	--The player might have items remaining in the slots from the previous join; this is likely
+	--when the server has been shutdown and the server didn't clean up the player inventories.
+	mcl_util.move_player_list(player, "cartography_table_input")
+	player:get_inventory():set_list("cartography_table_output", {})
+end)
+
+core.register_on_leaveplayer(function(player)
+	mcl_util.move_player_list(player, "cartography_table_input")
+	player:get_inventory():set_list("cartography_table_output", {})
+end)
+
+function remove_from_input(player, inventory, count)
+	local meta = player:get_meta()
+	local astack = inventory:get_stack("cartography_table_input", 1)
+	if astack then
+		astack:set_count(math.max(0, astack:get_count() - count))
+		inventory:set_stack("cartography_table_input", 1, astack)
+	end
+	local bstack = inventory:get_stack("cartography_table_input", 2)
+	if bstack then
+		bstack:set_count(math.max(0, bstack:get_count() - count))
+		inventory:set_stack("cartography_table_input", 2, bstack)
+	end
+end
+
+core.register_allow_player_inventory_action(function(player, action, inventory, inventory_info)
+	-- Generate zoomed map
+	if (action == "move" or action == "take") and inventory_info.from_list == "cartography_table_output" and inventory_info.from_index == 1 then
+		local stack = inventory:get_stack("cartography_table_output", 1)
+		local addon = inventory:get_stack("cartography_table_input", 2)
+		if stack:get_name():find("mcl_maps:filled_map") and addon:get_name() == "mcl_core:paper" then
+			local pname = player:get_player_name()
+			core.chat_send_player(pname, S("Zooming a map may take several seconds to generate the world, please wait."))
+			local callback = function(id, filename) core.chat_send_player(pname, S("The zoomed map is now ready.")) end
+			mcl_maps.regenerate_map(stack, callback) -- new zoom level
+			inventory:set_stack("cartography_table_output", 1, stack)
 		end
 	end
 
-	minetest.show_formspec(player:get_player_name(), "mcl_cartography_table", formspec)
-end
+	-- TODO: also allow map texture refresh?
+	if action == "move" or action == "put" then
+		if inventory_info.to_list == "cartography_table_output" then return false end
+		if inventory_info.to_list == "cartograhy_table_input" then
+			local index = inventory_info.to_index
+			local stack = inventory:get_stack("cartography_table_input", index)
+			if index == 1 and stack:get_name() == "mcl_maps:empty_map" then return inventory_info.count end
+			if index == 1 and stack:get_name():find("mcl_maps:filled_map") then return inventory_info.count end
+			if index == 1 and stack:get_name() == "mcl_core:paper" then return inventory_info.count end
+			if index == 2 and stack:get_name() == "mcl_maps:empty_map" then return inventory_info.count end
+			if index == 2 and stack:get_name() == "xpanes:pane_natural_flat" then return inventory_info.count end
+			return false
+		end
+		if inventory_info.from_list == "cartography_table_output" and inventory_info.from_index == 1 then
+			return inventory_info.count
+		end
+	end
+end)
 
-local allowed_to_put = {
-	--["mcl_core:paper"] = true, Requires missing features with increasing map size
-	["mcl_maps:empty_map"] = true,
-	["xpanes:pane_natural_flat"] = true
-}
+core.register_on_player_inventory_action(function(player, action, inventory, inventory_info)
+	if action == "move" then
+		if inventory_info.from_list == "cartography_table_output" then
+			remove_from_input(player, inventory, inventory_info.count)
+		end
+		if inventory_info.to_list == "cartography_table_input" or inventory_info.from_list == "cartography_table_input" then
+			update_cartography_table(player)
+		end
+	elseif action == "put" then
+		if inventory_info.listname == "cartography_table_input" then
+			update_cartography_table(player)
+		end
+	elseif action == "take" then
+		if inventory_info.listname == "cartography_table_output" then
+			remove_from_input(player, inventory, inventory_info.stack:get_count())
+		end
+	end
+end)
 
-minetest.register_node("mcl_cartography_table:cartography_table", {
+core.register_on_player_receive_fields(function(player, formname, fields)
+	if formname ~= formspec_name then return end
+	if fields.quit then
+		mcl_util.move_player_list(player, "cartography_table_input")
+		player:get_inventory():set_list("cartography_table_output", {})
+		return
+	end
+end)
+
+core.register_node("mcl_cartography_table:cartography_table", {
 	description = S("Cartography Table"),
-	_tt_help = S("Used to create or copy maps"),
-	_doc_items_longdesc = S("Is used to create or copy maps for use."),
+	_tt_help = S("Used to copy, lock, and zoom maps"),
+	_doc_items_longdesc = S("A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and maps may only be zoomed out once right now."),
 	tiles = {
 		"mcl_cartography_table_top.png", "mcl_cartography_table_side3.png",
 		"mcl_cartography_table_side3.png", "mcl_cartography_table_side2.png",
@@ -99,60 +211,12 @@ minetest.register_node("mcl_cartography_table:cartography_table", {
 	sounds = mcl_sounds.node_sound_wood_defaults(),
 	_mcl_blast_resistance = 2.5,
 	_mcl_hardness = 2.5,
-	on_construct = function(pos)
-		local inv = minetest.get_meta(pos):get_inventory()
-		inv:set_size("input", 2)
-		inv:set_size("output", 1)
-	end,
-	allow_metadata_inventory_put = function(pos, listname, index, stack, player)
-		if minetest.is_protected(pos, player:get_player_name()) or listname == "output" then
-			return 0
-		else
-			if index == 2 and not stack:get_name():find("filled_map") then return 0 end
-			if index == 1 and not allowed_to_put[stack:get_name()] then return 0 end
-			return stack:get_count()
-		end
-	end,
-	on_metadata_inventory_put = function(pos, _, _, _, player)
-		refresh_cartography(pos, player)
-	end,
-	on_metadata_inventory_take = function(pos, listname, _, _, player)
-		local inv = minetest.get_meta(pos):get_inventory()
-		if listname == "output" then
-			local first = inv:get_stack("input", 2); first:take_item(); inv:set_stack("input", 2, first)
-			local second = inv:get_stack("input", 1); second:take_item(); inv:set_stack("input", 1, second)
-		else
-			inv:set_stack("output", 1, "")
-		end
-		refresh_cartography(pos, player)
-	end,
-	allow_metadata_inventory_move = function() return 0 end,
-	allow_metadata_inventory_take = function(pos, listname, index, stack, player)
-		return 0 and minetest.is_protected(pos, player:get_player_name()) or stack:get_count()
-	end,
 	on_rightclick = function(pos, node, player, itemstack)
-		if not player:get_player_control().sneak then refresh_cartography(pos, player) end
-	end,
-	after_dig_node = function(pos, _, oldmetadata, _)
-		local meta = minetest.get_meta(pos)
-		local meta2 = meta:to_table()
-		meta:from_table(oldmetadata)
-		local inv = meta:get_inventory()
-		for i = 1, inv:get_size("input") do
-			local stack = inv:get_stack("input", i)
-			if not stack:is_empty() then
-				minetest.add_item(vector.offset(pos,
-					math.random(0, 10) / 10 - 0.5,
-					0,
-					math.random(0, 10) / 10 - 0.5
-				), stack)
-			end
-		end
-		meta:from_table(meta2)
+		if player and player:is_player() and not player:get_player_control().sneak then update_cartography_table(player) end
 	end,
 })
 
-minetest.register_craft({
+core.register_craft({
 	output = "mcl_cartography_table:cartography_table",
 	recipe = {
 		{ "mcl_core:paper", "mcl_core:paper", "" },
@@ -161,7 +225,7 @@ minetest.register_craft({
 	}
 })
 
-minetest.register_craft({
+core.register_craft({
 	type = "fuel",
 	recipe = "mcl_cartography_table:cartography_table",
 	burntime = 15,
diff --git a/mods/ITEMS/mcl_cartography_table/mod.conf b/mods/ITEMS/mcl_cartography_table/mod.conf
index 5b57c0b6b..c36195444 100644
--- a/mods/ITEMS/mcl_cartography_table/mod.conf
+++ b/mods/ITEMS/mcl_cartography_table/mod.conf
@@ -1,4 +1,4 @@
 name = mcl_cartography_table
-author = PrairieWind, mirqf, AFCM
-depends = mcl_core, mcl_sounds, mcl_tools, mcl_formspec
-description = Adds the cartography table villager workstation to MineClone 2/5. Used to copy and create maps.
\ No newline at end of file
+author = PrairieWind, mirqf, AFCM, kno10
+depends = mcl_core, mcl_sounds, mcl_tools, mcl_formspec, tt, mcl_maps
+description = Adds the cartography table villager workstation to Voxelibre. Used to copy and create maps.
diff --git a/mods/ITEMS/mcl_maps/init.lua b/mods/ITEMS/mcl_maps/init.lua
index 00f6ba5c9..e5c13fa20 100644
--- a/mods/ITEMS/mcl_maps/init.lua
+++ b/mods/ITEMS/mcl_maps/init.lua
@@ -1,189 +1,269 @@
+-- TODO: improve support for larger zoom levels, maybe by NOT using vmanip but rather raycasting?
+-- TODO: only send texture to players that have the map
+-- TODO: use ephemeral textures or base64 inline textures to eventually allow explorer maps?
+-- TODO: show multiple players on the map
+-- TODO: show banners on map
+-- Check for engine updates that allow improvements
 mcl_maps = {}
 
-local modname = minetest.get_current_modname()
-local modpath = minetest.get_modpath(modname)
-local S = minetest.get_translator(modname)
+mcl_maps.max_zoom = 2 -- level 3 already may take some 20 minutes...
+mcl_maps.enable_maps = core.settings:get_bool("enable_real_maps", true)
+mcl_maps.allow_nether_maps = core.settings:get_bool("vl_maps_allow_nether", true)
+mcl_maps.map_allow_overlap = core.settings:get_bool("vl_maps_allow_overlap", true) -- 50% overlap allowed in each level
+
+local modname = core.get_current_modname()
+local modpath = core.get_modpath(modname)
+local S = core.get_translator(modname)
 
-local math = math
 local vector = vector
 local table = table
 local pairs = pairs
+local min, max, round, floor, ceil = math.min, math.max, math.round, math.floor, math.ceil
+local HALF_PI = math.pi * 0.5
 
-local pos_to_string = minetest.pos_to_string
-local string_to_pos = minetest.string_to_pos
-local get_item_group = minetest.get_item_group
-local dynamic_add_media = minetest.dynamic_add_media
-local get_connected_players = minetest.get_connected_players
+local pos_to_string = core.pos_to_string
+local string_to_pos = core.string_to_pos
+local get_item_group = core.get_item_group
+local dynamic_add_media = core.dynamic_add_media
+local get_connected_players = core.get_connected_players
 
-local storage = minetest.get_mod_storage()
-local worldpath = minetest.get_worldpath()
+local storage = core.get_mod_storage()
+local worldpath = core.get_worldpath()
 local map_textures_path = worldpath .. "/mcl_maps/"
---local last_finished_id = storage:get_int("next_id") - 1
 
-minetest.mkdir(map_textures_path)
+core.mkdir(map_textures_path)
 
 local function load_json_file(name)
 	local file = assert(io.open(modpath .. "/" .. name .. ".json", "r"))
-	local data = minetest.parse_json(file:read("*all"))
+	local data = core.parse_json(file:read("*all"))
 	file:close()
 	return data
 end
 
 local texture_colors = load_json_file("colors")
 
-local creating_maps = {}
-local loaded_maps = {}
+local maps_generating, maps_loading = {}, {}
 
-local c_air = minetest.get_content_id("air")
+local c_air = core.get_content_id("air")
 
-function mcl_maps.create_map(pos)
-	local minp = vector.multiply(vector.floor(vector.divide(pos, 128)), 128)
-	local maxp = vector.add(minp, vector.new(127, 127, 127))
+local function generate_map(id, minp, maxp, callback)
+	if maps_generating[id] then return end
+	maps_generating[id] = true
+	-- FIXME: reduce resolution when zoomed out
+	local t1 = os.clock()
+	core.emerge_area(minp, maxp, function(blockpos, action, calls_remaining)
+		if calls_remaining > 0 then return end
+		-- do a DOUBLE emerge to give mapgen the chance to place structures triggered by the initial emerge
+		core.emerge_area(minp, maxp, function(blockpos, action, calls_remaining)
+			if calls_remaining > 0 then return end
+
+			-- Load voxelmanip, measure time as this is fairly expensive
+			local t2 = os.clock()
+			local vm = core.get_voxel_manip()
+			local emin, emax = vm:read_from_map(minp, maxp)
+			local data = vm:get_data()
+			local param2data = vm:get_param2_data()
+			local t3 = os.clock()
+
+			-- Generate a (usually) 128x128 linear array for the image
+			local pixels = {}
+			local area = VoxelArea:new({ MinEdge = emin, MaxEdge = emax })
+			local xsize, zsize = maxp.x - minp.x + 1, maxp.z - minp.z + 1
+			-- Step size, for zoom levels > 0
+			local xstep, zstep = ceil(xsize / 128), ceil(zsize / 128)
+			local ystride = area.ystride
+			for z = zsize, 1, -zstep do
+				local map_z = minp.z  + z - 1
+				local last_height
+				for x = 1, xsize, xstep do
+					local map_x = minp.x + x - 1
+
+					-- color aggregate and height information (for 3D effect)
+					local cagg, height = { 0, 0, 0, 0 }, nil
+					local solid_under_air = -1 -- anything but air, actually
+					local index = area:index(map_x, maxp.y, map_z) + ystride
+					for map_y = maxp.y, minp.y, -1 do
+						index = index - ystride -- vertically down until we are opaque
+
+						local c_id = data[index]
+						if c_id ~= c_air then
+							local color = texture_colors[core.get_name_from_content_id(c_id)]
+							-- use param2 if available:
+							if color and type(color[1]) == "table" then
+								color = color[param2data[index] + 1] or color[1]
+							end
+							if color then
+								if solid_under_air == 0 then
+									cagg = { 0, 0, 0, 0 } -- reset
+									solid_under_air = 1
+								end
+								local alpha = cagg[4] -- 0 (transparent) to 255 (opaque)
+								local a = (color[4] or 255) * (255 - alpha) / 255 -- 0 to 255
+								local f = a / 255 -- 0 to 1, color contribution
+								-- Alpha blend the colors:
+								cagg[1] = cagg[1] + f * color[1]
+								cagg[2] = cagg[2] + f * color[2]
+								cagg[3] = cagg[3] + f * color[3]
+								alpha = cagg[4] + a -- new alpha, 0 to 255
+								cagg[4] = alpha
+
+								-- ground estimate with transparent blocks
+								if alpha > 140 and not height then height = map_y end
+								if alpha >= 250 then
+									-- adjust color to give a 3d effect
+									if last_height and height then
+										local dheight = max(-48, min((height - last_height) * 8, 48))
+										cagg[1] = cagg[1] + dheight
+										cagg[2] = cagg[2] + dheight
+										cagg[3] = cagg[3] + dheight
+									end
+									cagg[4] = 255 -- make fully opaque
+									break
+								end
+							end
+						elseif solid_under_air == -1 then
+							solid_under_air = 0
+						end
+					end
+					-- clamp colors values to 0:255 for PNG
+					-- because 3d height effect may exceed this range
+					cagg[1] = max(0, min(round(cagg[1]), 255))
+					cagg[2] = max(0, min(round(cagg[2]), 255))
+					cagg[3] = max(0, min(round(cagg[3]), 255))
+					cagg[4] = max(0, min(round(cagg[4]), 255))
+					pixels[#pixels + 1] = string.char(cagg[1], cagg[2], cagg[3], cagg[4])
+					last_height = height
+				end
+			end
+			-- Save as png texture
+			local filename = map_textures_path .. "mcl_maps_map_" .. id .. ".png"
+			local data = core.encode_png(xsize / xstep, zsize / zstep, table.concat(pixels))
+			local f = assert(io.open(filename, "wb"))
+			f:write(data)
+			f:close()
+			-- core.log("action", string.format("Completed map %s after %.2fms (%.2fms emerge, %.2fms LVM, %.2fms map)", id, (os.clock()-t1)*1000, (t2-t1)*1000, (t3-t2)*1000, (os.clock()-t3)*1000))
+			maps_generating[id] = nil
+			if callback then callback(id, filename) end
+		end)
+	end)
+end
+
+local function configure_map(itemstack, cx, dim, cz, zoom, callback)
+	zoom = zoom or 0
+	-- Texture size is 128
+	local size = 128 * (2^zoom)
+	local halfsize = size / 2
+	-- If enabled, round to halfsize grid, otherwise to size grid.
+	if mcl_maps.map_allow_overlap then
+		cx, cz = (floor(cx / halfsize) + 0.5) * halfsize, (floor(cz / halfsize) + 0.5) * halfsize
+	else
+		cx, cz = (floor(cx / size) + 0.5) * size, (floor(cz / size) + 0.5) * size
+	end
+	-- Y range to use for mapping. In nether, if we begin above bedrock, maps will be bedrock only, similar to MC
+	-- Prefer smaller ranges for performance!
+	local miny, maxy
+	if dim == "end" then
+		miny, maxy = mcl_vars.mg_end_min + 48, mcl_vars.mg_end_min + 127
+	elseif dim == "nether" then
+		if mcl_maps.allow_nether_maps then
+			miny, maxy = mcl_vars.mg_nether_min + 16, mcl_vars.mg_nether_deco_max
+		else
+			miny, maxy = mcl_vars.mg_nether_max, mcl_vars.mg_nether_max -- map the nether roof...
+		end
+	elseif dim == "overworld" then
+		miny, maxy = -32, 63
+	else
+		miny = tonumber(dim) - 32
+		maxy = miny + 63
+	end
+
+	-- File name conventions, including a unique number in case someone maps the same area twice (old and new)
+	local seq = storage:get_int("next_id")
+	storage:set_int("next_id", seq + 1)
+	local id = table.concat({cx, dim, cz, zoom, seq}, "_")
+	local minp = vector.new(cx - halfsize, miny, cz - halfsize)
+	local maxp = vector.new(cx + halfsize - 1, maxy, cz + halfsize - 1)
 
-	local itemstack = ItemStack("mcl_maps:filled_map")
 	local meta = itemstack:get_meta()
-	local next_id = storage:get_int("next_id")
-	storage:set_int("next_id", next_id + 1)
-	local id = tostring(next_id)
 	meta:set_string("mcl_maps:id", id)
+	meta:set_int("mcl_maps:cx", cx)
+	meta:set_string("mcl_maps:dim", dim)
+	meta:set_int("mcl_maps:cz", cz)
+	meta:set_int("mcl_maps:zoom", zoom)
 	meta:set_string("mcl_maps:minp", pos_to_string(minp))
 	meta:set_string("mcl_maps:maxp", pos_to_string(maxp))
 	tt.reload_itemstack_description(itemstack)
 
-	creating_maps[id] = true
-	minetest.emerge_area(minp, maxp, function(blockpos, action, calls_remaining)
-		if calls_remaining > 0 then
-			return
-		end
-		local vm = minetest.get_voxel_manip()
-		local emin, emax = vm:read_from_map(minp, maxp)
-		local data = vm:get_data()
-		local param2data = vm:get_param2_data()
-		local area = VoxelArea:new({ MinEdge = emin, MaxEdge = emax })
-		local pixels = {}
-		for z = 1, 128 do
-			local map_z = minp.z - 1 + z
-			local last_height
-			for x = 1, 128 do
-				local map_x = minp.x - 1 + x
-				local cagg, alpha, height = { 0, 0, 0 }, 0
-				for map_y = maxp.y, minp.y, -1 do
-					local index = area:index(map_x, map_y, map_z)
-					local c_id = data[index]
-					if c_id ~= c_air then
-						local color = texture_colors[minetest.get_name_from_content_id(c_id)]
-						-- use param2 if available:
-						if color and type(color[1]) == "table" then
-							color = color[param2data[index] + 1] or color[1]
-						end
-						if color then
-							local a = (color[4] or 255) / 255
-							local f = a * (1 - alpha)
-							cagg[1] = cagg[1] + f * color[1]
-							cagg[2] = cagg[2] + f * color[2]
-							cagg[3] = cagg[3] + f * color[3]
-							alpha = alpha + f
-
-							-- ground estimate with transparent blocks
-							if alpha > 0.70 and not height then height = map_y end
-							-- adjust color to give a 3d effect
-							if alpha >= 0.99 and last_height and height then
-								local dheight = math.min(math.max((height - last_height) * 8, -32), 32)
-								cagg = {
-									math.max(0, math.min(255, cagg[1] + dheight)),
-									math.max(0, math.min(255, cagg[2] + dheight)),
-									math.max(0, math.min(255, cagg[3] + dheight)),
-								}
-							end
-							if alpha >= 0.99 then break end
-						end
-					end
-				end
-				last_height = height
-				pixels[z] = pixels[z] or {}
-				pixels[z][x] = cagg or { 0, 0, 0 }
-			end
-		end
-		tga_encoder.image(pixels):save(map_textures_path .. "mcl_maps_map_texture_" .. id .. ".tga")
-		creating_maps[id] = nil
-	end)
+	generate_map(id, minp, maxp, callback)
 	return itemstack
 end
 
 function mcl_maps.load_map(id, callback)
-	if id == "" or creating_maps[id] then
-		return false
-	end
+	if id == "" or maps_generating[id] then return false end
 
-	local texture = "mcl_maps_map_texture_" .. id .. ".tga"
-
-	local result = true
-
-	if not loaded_maps[id] then
-		if not minetest.features.dynamic_add_media_table then
-			-- minetest.dynamic_add_media() blocks in
-			-- Luanti 5.3 and 5.4 until media loads
-			loaded_maps[id] = true
-			result = dynamic_add_media(map_textures_path .. texture, function()
-			end)
-			if callback then
-				callback(texture)
-			end
-		else
-			-- minetest.dynamic_add_media() never blocks
-			-- in Luanti 5.5, callback runs after load
-			result = dynamic_add_media(map_textures_path .. texture, function()
-				loaded_maps[id] = true
-				if callback then
-					callback(texture)
-				end
-			end)
-		end
-	end
-
-	if result == false then
-		return false
-	end
-
-	if loaded_maps[id] then
-		if callback then
-			callback(texture)
-		end
+	local texture = "mcl_maps_map_" .. id .. ".png"
+	if maps_loading[id] then
+		if callback then callback(texture) end
 		return texture
 	end
+
+	-- core.dynamic_add_media() never blocks in Minetest 5.5, callback runs after load
+	-- TODO: send only to the player that needs it!
+	dynamic_add_media(map_textures_path .. texture, function()
+		if not maps_loading[id] then -- avoid repeated callbacks
+			maps_loading[id] = true
+			if callback then callback(texture) end
+		end
+	end)
 end
 
-function mcl_maps.load_map_item(itemstack)
-	return mcl_maps.load_map(itemstack:get_meta():get_string("mcl_maps:id"))
+function mcl_maps.create_map(pos, zoom, callback)
+	local dim = mcl_worlds.pos_to_dimension(pos)
+	if dim == "overworld" and pos.y >= 48 then dim = tostring(round(pos.y/64)*64) end -- for float islands
+	local itemstack = ItemStack("mcl_maps:filled_map")
+	configure_map(itemstack, pos.x, dim, pos.z, zoom, callback)
+	return itemstack
+end
+
+function mcl_maps.load_map_item(itemstack, callback)
+	return mcl_maps.load_map(itemstack:get_meta():get_string("mcl_maps:id"), callback)
+end
+
+function mcl_maps.regenerate_map(itemstack, callback)
+	local meta = itemstack:get_meta()
+	local cx, cz = meta:get_int("mcl_maps:cx"), meta:get_int("mcl_maps:cz")
+	local dim = meta:get_string("mcl_maps:dim")
+	local zoom = meta:get_int("mcl_maps:zoom")
+	if mcl_maps.enable_maps then
+		configure_map(itemstack, cx, dim, cz, zoom, callback)
+	end
 end
 
 local function fill_map(itemstack, placer, pointed_thing)
 	local new_stack = mcl_util.call_on_rightclick(itemstack, placer, pointed_thing)
-	if new_stack then
-		return new_stack
-	end
+	if new_stack then return new_stack end
 
-	if minetest.settings:get_bool("enable_real_maps", true) then
-		local new_map = mcl_maps.create_map(placer:get_pos())
+	if mcl_maps.enable_maps then
+		local pname = placer:get_player_name()
+		core.chat_send_player(pname, S("It may take a moment for the map to be ready."))
+		local callback = function(id, filename) core.chat_send_player(pname, S("The new map is now ready.")) end
+		local new_map = mcl_maps.create_map(placer:get_pos(), 0, callback)
 		itemstack:take_item()
-		if itemstack:is_empty() then
-			return new_map
+		if itemstack:is_empty() then return new_map end
+		local inv = placer:get_inventory()
+		if inv:room_for_item("main", new_map) then
+			inv:add_item("main", new_map)
 		else
-			local inv = placer:get_inventory()
-			if inv:room_for_item("main", new_map) then
-				inv:add_item("main", new_map)
-			else
-				minetest.add_item(placer:get_pos(), new_map)
-			end
-			return itemstack
+			core.add_item(placer:get_pos(), new_map)
 		end
+		return itemstack
 	end
 end
 
-minetest.register_craftitem("mcl_maps:empty_map", {
+core.register_craftitem("mcl_maps:empty_map", {
 	description = S("Empty Map"),
 	_doc_items_longdesc = S("Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used."),
-	_doc_items_usagehelp = S("Rightclick to create a filled map (which can't be stacked anymore)."),
+	_doc_items_usagehelp = S("Rightclick to create a filled map (which cannot be stacked anymore)."),
 	inventory_image = "mcl_maps_map_empty.png",
 	on_place = fill_map,
 	on_secondary_use = fill_map,
@@ -200,10 +280,10 @@ local filled_def = {
 	groups = { not_in_creative_inventory = 1, filled_map = 1, tool = 1 },
 }
 
-minetest.register_craftitem("mcl_maps:filled_map", filled_def)
+core.register_craftitem("mcl_maps:filled_map", filled_def)
 
 local filled_wield_def = table.copy(filled_def)
-filled_wield_def.use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "opaque" or false
+filled_wield_def.use_texture_alpha = core.features.use_texture_alpha_string_modes and "opaque" or false
 filled_wield_def.visual_scale = 1
 filled_wield_def.wield_scale = { x = 1, y = 1, z = 1 }
 filled_wield_def.paramtype = "light"
@@ -212,7 +292,7 @@ filled_wield_def.node_placement_prediction = ""
 filled_wield_def.on_place = mcl_util.call_on_rightclick
 filled_wield_def._mcl_wieldview_item = "mcl_maps:filled_map"
 
-local mcl_skins_enabled = minetest.global_exists("mcl_skins")
+local mcl_skins_enabled = core.global_exists("mcl_skins")
 
 if mcl_skins_enabled then
 	-- Generate a node for every skin
@@ -223,26 +303,26 @@ if mcl_skins_enabled then
 			female._mcl_hand_id = skin.id
 			female.mesh = "mcl_meshhand_female.b3d"
 			female.tiles = { skin.texture }
-			minetest.register_node("mcl_maps:filled_map_" .. skin.id, female)
+			core.register_node("mcl_maps:filled_map_" .. skin.id, female)
 		else
 			local male = table.copy(filled_wield_def)
 			male._mcl_hand_id = skin.id
 			male.mesh = "mcl_meshhand.b3d"
 			male.tiles = { skin.texture }
-			minetest.register_node("mcl_maps:filled_map_" .. skin.id, male)
+			core.register_node("mcl_maps:filled_map_" .. skin.id, male)
 		end
 	end
 else
 	filled_wield_def._mcl_hand_id = "hand"
 	filled_wield_def.mesh = "mcl_meshhand.b3d"
 	filled_wield_def.tiles = { "character.png" }
-	minetest.register_node("mcl_maps:filled_map_hand", filled_wield_def)
+	core.register_node("mcl_maps:filled_map_hand", filled_wield_def)
 end
 
-local old_add_item = minetest.add_item
-function minetest.add_item(pos, stack)
+local old_add_item = core.add_item
+function core.add_item(pos, stack)
 	if not pos then
-		minetest.log("warning", "Trying to add item with missing pos: " .. tostring(stack))
+		core.log("warning", "Trying to add item with missing pos: " .. tostring(stack))
 		return
 	end
 	stack = ItemStack(stack)
@@ -254,23 +334,23 @@ end
 
 tt.register_priority_snippet(function(itemstring, _, itemstack)
 	if itemstack and get_item_group(itemstring, "filled_map") > 0 then
-		local id = itemstack:get_meta():get_string("mcl_maps:id")
-		if id ~= "" then
-			return "#" .. id, mcl_colors.GRAY
+		local zoom = itemstack:get_meta():get_string("mcl_maps:zoom")
+		if zoom ~= "" then
+			return S("Level @1", zoom), mcl_colors.GRAY
 		end
 	end
 end)
 
-minetest.register_craft({
+core.register_craft({
 	output = "mcl_maps:empty_map",
 	recipe = {
 		{ "mcl_core:paper", "mcl_core:paper", "mcl_core:paper" },
-		{ "mcl_core:paper", "group:compass", "mcl_core:paper" },
+		{ "mcl_core:paper", "group:compass",  "mcl_core:paper" },
 		{ "mcl_core:paper", "mcl_core:paper", "mcl_core:paper" },
 	}
 })
 
-minetest.register_craft({
+core.register_craft({
 	type = "shapeless",
 	output = "mcl_maps:filled_map 2",
 	recipe = { "group:filled_map", "mcl_maps:empty_map" },
@@ -287,13 +367,13 @@ local function on_craft(itemstack, player, old_craft_grid, craft_inv)
 	end
 end
 
-minetest.register_on_craft(on_craft)
-minetest.register_craft_predict(on_craft)
+core.register_on_craft(on_craft)
+core.register_craft_predict(on_craft)
 
 local maps = {}
 local huds = {}
 
-minetest.register_on_joinplayer(function(player)
+core.register_on_joinplayer(function(player)
 	local map_def = {
 		hud_elem_type = "image",
 		text = "blank.png",
@@ -310,17 +390,17 @@ minetest.register_on_joinplayer(function(player)
 	}
 end)
 
-minetest.register_on_leaveplayer(function(player)
+core.register_on_leaveplayer(function(player)
 	maps[player] = nil
 	huds[player] = nil
 end)
 
-minetest.register_globalstep(function(dtime)
+core.register_globalstep(function(dtime)
 	for _, player in pairs(get_connected_players()) do
 		local wield = player:get_wielded_item()
 		local texture = mcl_maps.load_map_item(wield)
-		local hud = huds[player]
 		if texture then
+			local hud = huds[player]
 			local wield_def = wield:get_definition()
 			local hand_def = player:get_inventory():get_stack("hand", 1):get_definition()
 
@@ -329,18 +409,21 @@ minetest.register_globalstep(function(dtime)
 				player:set_wielded_item(wield)
 			end
 
-			if texture ~= maps[player] then
+			-- change map only when necessary
+			if not maps[player] or texture ~= maps[player][1] then
 				player:hud_change(hud.map, "text", "[combine:140x140:0,0=mcl_maps_map_background.png:6,6=" .. texture)
-				maps[player] = texture
+				local meta = wield:get_meta()
+				local minp = string_to_pos(meta:get_string("mcl_maps:minp"))
+				local maxp = string_to_pos(meta:get_string("mcl_maps:maxp"))
+				maps[player] = {texture, minp, maxp}
 			end
 
-			local pos = vector.round(player:get_pos())
-			local meta = wield:get_meta()
-			local minp = string_to_pos(meta:get_string("mcl_maps:minp"))
-			local maxp = string_to_pos(meta:get_string("mcl_maps:maxp"))
+			-- ,ap overlay with player position
+			local pos = player:get_pos() -- was: vector.round(player:get_pos())
+			local minp, maxp = maps[player][2], maps[player][3]
 
+			-- Use dots when outside of map, indicate direction
 			local marker = "mcl_maps_player_arrow.png"
-
 			if pos.x < minp.x then
 				marker = "mcl_maps_player_dot.png"
 				pos.x = minp.x
@@ -358,13 +441,17 @@ minetest.register_globalstep(function(dtime)
 			end
 
 			if marker == "mcl_maps_player_arrow.png" then
-				local yaw = (math.floor(player:get_look_horizontal() * 180 / math.pi / 90 + 0.5) % 4) * 90
+				local yaw = (floor(player:get_look_horizontal() / HALF_PI + 0.5) % 4) * 90
 				marker = marker .. "^[transformR" .. yaw
 			end
 
+			-- Note the alignment and scale used above
+			local f = 2 * 128 / (maxp.x - minp.x + 1)
+			player:hud_change(hud.marker, "offset", { x = (pos.x - minp.x) * f - 128, y = (maxp.z - pos.z) * f - 256 })
 			player:hud_change(hud.marker, "text", marker)
-			player:hud_change(hud.marker, "offset", { x = (6 - 140 / 2 + pos.x - minp.x) * 2, y = (6 - 140 + maxp.z - pos.z) * 2 })
-		elseif maps[player] then
+
+		elseif maps[player] then -- disable map
+			local hud = huds[player]
 			player:hud_change(hud.map, "text", "blank.png")
 			player:hud_change(hud.marker, "text", "blank.png")
 			maps[player] = nil
diff --git a/mods/ITEMS/mcl_maps/mod.conf b/mods/ITEMS/mcl_maps/mod.conf
index e1f068963..d5211ed14 100644
--- a/mods/ITEMS/mcl_maps/mod.conf
+++ b/mods/ITEMS/mcl_maps/mod.conf
@@ -1,2 +1,3 @@
 name = mcl_maps
-depends = mcl_core, mcl_flowers, tga_encoder, tt, mcl_colors, mcl_skins, mcl_util
+author = wuzzy, fleckenstein, kno10
+depends = mcl_core, mcl_flowers, tt, mcl_colors, mcl_skins, mcl_util
diff --git a/settingtypes.txt b/settingtypes.txt
index 2b5901095..aec3f97c0 100644
--- a/settingtypes.txt
+++ b/settingtypes.txt
@@ -320,6 +320,12 @@ fix_doubleplants (Mcimport double plant fixes) bool true
 # Allow players to create Minecraft-like maps.
 enable_real_maps (Enable Real Maps) bool true
 
+# Make maps in the Nether show not only the bedrock.
+vl_maps_allow_nether (Make maps in the Nether useful) bool true
+
+# Allow maps to overlap 50% per level
+vl_maps_allow_overlap (Allow overlapping maps by 50%) bool true
+
 # Enable workarounds for faulty mob navigation.
 # Hack 1: teleport golems home if they are very far from home
 mcl_mob_allow_nav_hacks (Mob navigation hacks) bool false

From 280e820fe31e4abe9ded853f9e99a50a86b2b7c3 Mon Sep 17 00:00:00 2001
From: kno10 <erich.schubert@gmail.com>
Date: Sun, 12 Jan 2025 16:16:49 +0100
Subject: [PATCH 3/5] map improvements

---
 mods/ITEMS/mcl_cartography_table/init.lua | 11 +++---
 mods/ITEMS/mcl_maps/init.lua              | 42 +++++++++++++----------
 mods/ITEMS/mcl_maps/mod.conf              |  2 +-
 settingtypes.txt                          |  3 ++
 4 files changed, 34 insertions(+), 24 deletions(-)

diff --git a/mods/ITEMS/mcl_cartography_table/init.lua b/mods/ITEMS/mcl_cartography_table/init.lua
index 3cc2f6fb5..ab45cb696 100644
--- a/mods/ITEMS/mcl_cartography_table/init.lua
+++ b/mods/ITEMS/mcl_cartography_table/init.lua
@@ -142,9 +142,12 @@ core.register_allow_player_inventory_action(function(player, action, inventory,
 		local stack = inventory:get_stack("cartography_table_output", 1)
 		local addon = inventory:get_stack("cartography_table_input", 2)
 		if stack:get_name():find("mcl_maps:filled_map") and addon:get_name() == "mcl_core:paper" then
-			local pname = player:get_player_name()
-			core.chat_send_player(pname, S("Zooming a map may take several seconds to generate the world, please wait."))
-			local callback = function(id, filename) core.chat_send_player(pname, S("The zoomed map is now ready.")) end
+			-- also send chat, as the actionbar may be hidden by the cartograph table
+			core.chat_send_player(player:get_player_name(), core.get_color_escape_sequence("gold")..S("Zooming a map may take several seconds to generate the world, please wait."))
+			mcl_title.set(player, "actionbar", {text=S("Zooming a map may take several seconds to generate the world, please wait."), color="gold", stay=5*20})
+			local callback = function(id, filename)
+				mcl_title.set(player, "actionbar", {text=S("The zoomed map is now ready."), color="green", stay=3*20})
+			end
 			mcl_maps.regenerate_map(stack, callback) -- new zoom level
 			inventory:set_stack("cartography_table_output", 1, stack)
 		end
@@ -200,7 +203,7 @@ end)
 core.register_node("mcl_cartography_table:cartography_table", {
 	description = S("Cartography Table"),
 	_tt_help = S("Used to copy, lock, and zoom maps"),
-	_doc_items_longdesc = S("A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and maps may only be zoomed out once right now."),
+	_doc_items_longdesc = S("A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and the maximum zoom level may be restricted by server settings to limit the performance impact."),
 	tiles = {
 		"mcl_cartography_table_top.png", "mcl_cartography_table_side3.png",
 		"mcl_cartography_table_side3.png", "mcl_cartography_table_side2.png",
diff --git a/mods/ITEMS/mcl_maps/init.lua b/mods/ITEMS/mcl_maps/init.lua
index e5c13fa20..885becb03 100644
--- a/mods/ITEMS/mcl_maps/init.lua
+++ b/mods/ITEMS/mcl_maps/init.lua
@@ -6,7 +6,8 @@
 -- Check for engine updates that allow improvements
 mcl_maps = {}
 
-mcl_maps.max_zoom = 2 -- level 3 already may take some 20 minutes...
+-- level 4 already may take some 20 minutes...
+mcl_maps.max_zoom = (tonumber(core.settings:get("vl_maps_max_zoom")) or 2)
 mcl_maps.enable_maps = core.settings:get_bool("enable_real_maps", true)
 mcl_maps.allow_nether_maps = core.settings:get_bool("vl_maps_allow_nether", true)
 mcl_maps.map_allow_overlap = core.settings:get_bool("vl_maps_allow_overlap", true) -- 50% overlap allowed in each level
@@ -69,7 +70,7 @@ local function generate_map(id, minp, maxp, callback)
 			local pixels = {}
 			local area = VoxelArea:new({ MinEdge = emin, MaxEdge = emax })
 			local xsize, zsize = maxp.x - minp.x + 1, maxp.z - minp.z + 1
-			-- Step size, for zoom levels > 0
+			-- Step size, for zoom levels > 1
 			local xstep, zstep = ceil(xsize / 128), ceil(zsize / 128)
 			local ystride = area.ystride
 			for z = zsize, 1, -zstep do
@@ -94,22 +95,24 @@ local function generate_map(id, minp, maxp, callback)
 							end
 							if color then
 								if solid_under_air == 0 then
-									cagg = { 0, 0, 0, 0 } -- reset
+									cagg, height = { 0, 0, 0, 0 }, nil -- reset
 									solid_under_air = 1
 								end
 								local alpha = cagg[4] -- 0 (transparent) to 255 (opaque)
-								local a = (color[4] or 255) * (255 - alpha) / 255 -- 0 to 255
-								local f = a / 255 -- 0 to 1, color contribution
-								-- Alpha blend the colors:
-								cagg[1] = cagg[1] + f * color[1]
-								cagg[2] = cagg[2] + f * color[2]
-								cagg[3] = cagg[3] + f * color[3]
-								alpha = cagg[4] + a -- new alpha, 0 to 255
-								cagg[4] = alpha
+								if alpha < 255 then
+									local a = (color[4] or 255) * (255 - alpha) / 255 -- 0 to 255
+									local f = a / 255 -- 0 to 1, color contribution
+									-- Alpha blend the colors:
+									cagg[1] = cagg[1] + f * color[1]
+									cagg[2] = cagg[2] + f * color[2]
+									cagg[3] = cagg[3] + f * color[3]
+									alpha = cagg[4] + a -- new alpha, 0 to 255
+									cagg[4] = alpha
+								end
 
 								-- ground estimate with transparent blocks
 								if alpha > 140 and not height then height = map_y end
-								if alpha >= 250 then
+								if alpha >= 250 and solid_under_air > 0 then
 									-- adjust color to give a 3d effect
 									if last_height and height then
 										local dheight = max(-48, min((height - last_height) * 8, 48))
@@ -122,7 +125,7 @@ local function generate_map(id, minp, maxp, callback)
 								end
 							end
 						elseif solid_under_air == -1 then
-							solid_under_air = 0
+							solid_under_air = 0 -- first air
 						end
 					end
 					-- clamp colors values to 0:255 for PNG
@@ -149,9 +152,9 @@ local function generate_map(id, minp, maxp, callback)
 end
 
 local function configure_map(itemstack, cx, dim, cz, zoom, callback)
-	zoom = zoom or 0
+	zoom = max(zoom or 1, 1)
 	-- Texture size is 128
-	local size = 128 * (2^zoom)
+	local size = 64 * (2^zoom)
 	local halfsize = size / 2
 	-- If enabled, round to halfsize grid, otherwise to size grid.
 	if mcl_maps.map_allow_overlap then
@@ -244,9 +247,10 @@ local function fill_map(itemstack, placer, pointed_thing)
 	if new_stack then return new_stack end
 
 	if mcl_maps.enable_maps then
-		local pname = placer:get_player_name()
-		core.chat_send_player(pname, S("It may take a moment for the map to be ready."))
-		local callback = function(id, filename) core.chat_send_player(pname, S("The new map is now ready.")) end
+		mcl_title.set(placer, "actionbar", {text=S("It may take a moment for the map to be ready."), color="gold", stay=5*20})
+		local callback = function(id, filename)
+			mcl_title.set(placer, "actionbar", {text=S("The new map is now ready."), color="green", stay=3*20})
+		end
 		local new_map = mcl_maps.create_map(placer:get_pos(), 0, callback)
 		itemstack:take_item()
 		if itemstack:is_empty() then return new_map end
@@ -418,7 +422,7 @@ core.register_globalstep(function(dtime)
 				maps[player] = {texture, minp, maxp}
 			end
 
-			-- ,ap overlay with player position
+			-- map overlay with player position
 			local pos = player:get_pos() -- was: vector.round(player:get_pos())
 			local minp, maxp = maps[player][2], maps[player][3]
 
diff --git a/mods/ITEMS/mcl_maps/mod.conf b/mods/ITEMS/mcl_maps/mod.conf
index d5211ed14..6e50b0fb9 100644
--- a/mods/ITEMS/mcl_maps/mod.conf
+++ b/mods/ITEMS/mcl_maps/mod.conf
@@ -1,3 +1,3 @@
 name = mcl_maps
 author = wuzzy, fleckenstein, kno10
-depends = mcl_core, mcl_flowers, tt, mcl_colors, mcl_skins, mcl_util
+depends = mcl_core, mcl_flowers, tt, mcl_colors, mcl_skins, mcl_util, mcl_title
diff --git a/settingtypes.txt b/settingtypes.txt
index aec3f97c0..d93a647e9 100644
--- a/settingtypes.txt
+++ b/settingtypes.txt
@@ -326,6 +326,9 @@ vl_maps_allow_nether (Make maps in the Nether useful) bool true
 # Allow maps to overlap 50% per level
 vl_maps_allow_overlap (Allow overlapping maps by 50%) bool true
 
+# Maximum map zoom level permitted (high zoom levels can take very long, do not increase on multiplayer servers)
+vl_maps_max_zoom (Maximum Zoom Level) int 2 1 5
+
 # Enable workarounds for faulty mob navigation.
 # Hack 1: teleport golems home if they are very far from home
 mcl_mob_allow_nav_hacks (Mob navigation hacks) bool false

From 81fa127be85d1fef48faac489c6bf254b3578145 Mon Sep 17 00:00:00 2001
From: kno10 <erich.schubert@gmail.com>
Date: Sun, 8 Dec 2024 15:24:36 +0100
Subject: [PATCH 4/5] translation files update

---
 .../locale/mcl_cartography_table.de.tr            |  8 +++++---
 .../locale/mcl_cartography_table.fr.tr            |  7 +++++--
 .../locale/mcl_cartography_table.gl.tr            |  7 +++++--
 .../locale/mcl_cartography_table.ja.tr            |  7 +++++--
 .../locale/mcl_cartography_table.nb.tr            |  7 +++++--
 .../locale/mcl_cartography_table.pt_BR.tr         |  7 +++++--
 .../locale/mcl_cartography_table.ru.tr            |  7 +++++--
 .../locale/mcl_cartography_table.zh_CN.tr         |  7 +++++--
 .../mcl_cartography_table/locale/template.txt     |  7 +++++--
 mods/ITEMS/mcl_maps/locale/mcl_maps.de.tr         |  5 ++++-
 mods/ITEMS/mcl_maps/locale/mcl_maps.es.tr         | 10 ++++------
 mods/ITEMS/mcl_maps/locale/mcl_maps.fr.tr         |  5 ++++-
 mods/ITEMS/mcl_maps/locale/mcl_maps.gl.tr         |  6 +++++-
 mods/ITEMS/mcl_maps/locale/mcl_maps.ja.tr         |  5 ++++-
 mods/ITEMS/mcl_maps/locale/mcl_maps.nb.tr         |  5 ++++-
 mods/ITEMS/mcl_maps/locale/mcl_maps.pl.tr         |  5 ++++-
 mods/ITEMS/mcl_maps/locale/mcl_maps.pt_BR.tr      |  5 ++++-
 mods/ITEMS/mcl_maps/locale/mcl_maps.ru.tr         |  5 ++++-
 mods/ITEMS/mcl_maps/locale/mcl_maps.zh_CN.tr      | 15 ++++-----------
 mods/ITEMS/mcl_maps/locale/mcl_maps.zh_TW.tr      | 15 ++++-----------
 mods/ITEMS/mcl_maps/locale/template.txt           |  5 ++++-
 21 files changed, 94 insertions(+), 56 deletions(-)

diff --git a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.de.tr b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.de.tr
index 7c0a27dcb..d169cbb13 100644
--- a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.de.tr
+++ b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.de.tr
@@ -1,5 +1,7 @@
 # textdomain: mcl_cartography_table
 Cartography Table=Kartentisch
-Used to create or copy maps=Dient zum Erstellen und Kopieren von Karten
-# choose one below
-Is used to create or copy maps for use..=Dient dazu, Karten zu erstellen oder zu kopieren.
+Inventory=Inventar
+Zooming a map may take several seconds to generate the world, please wait.=Eine Karte zu vergrößern kann einige Sekunden benötigen, bitte warten.
+The zoomed map is now ready.=Die vergrößerte Karte ist jetzt bereit.
+Used to copy, lock, and zoom maps=Kann Karten kopieren, sperren und vergrößern.
+A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and the maximum zoom level may be restricted by server settings to limit the performance impact.=Ein Kartentisch ermöglicht es, Karten zu kopieren, zu sperren, oder zu vergrößern. Sperren ist noch nicht nützlich, und der maximale Zoomlevel kann in den Servereinstellungen begrenzt sein, um die Rechenzeit zu begrenzen.
diff --git a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.fr.tr b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.fr.tr
index 7f0b128d0..e739b7f85 100644
--- a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.fr.tr
+++ b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.fr.tr
@@ -1,4 +1,7 @@
 # textdomain: mcl_cartography_table
 Cartography Table=Table de cartographie
-Used to create or copy maps=Utilisée pour créer ou copier des cartes
-Is used to create or copy maps for use..=Est utilisée pour créer ou copier des cartes..
+Inventory=
+Zooming a map may take several seconds to generate the world, please wait.=
+The zoomed map is now ready.=
+Used to copy, lock, and zoom maps=
+A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and the maximum zoom level may be restricted by server settings to limit the performance impact.=
diff --git a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.gl.tr b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.gl.tr
index ac259699e..0c62fc9ac 100644
--- a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.gl.tr
+++ b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.gl.tr
@@ -1,4 +1,7 @@
 # textdomain: mcl_cartography_table
 Cartography Table=Mesa de Cartografia
-Used to create or copy maps=Empregada para criar ou copiar mapas
-Is used to create or copy maps for use..=É usada para criar ou copiar mapas para o seu  uso..
+Inventory=
+Zooming a map may take several seconds to generate the world, please wait.=
+The zoomed map is now ready.=
+Used to copy, lock, and zoom maps=
+A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and the maximum zoom level may be restricted by server settings to limit the performance impact.=
diff --git a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.ja.tr b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.ja.tr
index 91c01c2d0..f52a5c69e 100644
--- a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.ja.tr
+++ b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.ja.tr
@@ -1,4 +1,7 @@
 # textdomain: mcl_cartography_table
 Cartography Table=製図台
-Used to create or copy maps=地図の作成やコピーに使用
-Is used to create or copy maps for use..=地図の作成やコピーに使用します。
+Inventory=
+Zooming a map may take several seconds to generate the world, please wait.=
+The zoomed map is now ready.=
+Used to copy, lock, and zoom maps=
+A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and the maximum zoom level may be restricted by server settings to limit the performance impact.=
diff --git a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.nb.tr b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.nb.tr
index ebdf65385..dfc33016a 100644
--- a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.nb.tr
+++ b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.nb.tr
@@ -1,4 +1,7 @@
 # textdomain: mcl_cartography_table
 Cartography Table=Kartografbenk
-Used to create or copy maps=Brukes for å opprette eller kopiere kart
-Is used to create or copy maps for use..=Brukes for å opprette eller kopiere kart for ulikt bruk...
+Inventory=
+Zooming a map may take several seconds to generate the world, please wait.=
+The zoomed map is now ready.=
+Used to copy, lock, and zoom maps=
+A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and the maximum zoom level may be restricted by server settings to limit the performance impact.=
diff --git a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.pt_BR.tr b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.pt_BR.tr
index da5e8e0af..0c62fc9ac 100644
--- a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.pt_BR.tr
+++ b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.pt_BR.tr
@@ -1,4 +1,7 @@
 # textdomain: mcl_cartography_table
 Cartography Table=Mesa de Cartografia
-Used to create or copy maps=Utilizada para criar ou copiar mapas
-Is used to create or copy maps for use..=É usada para criar ou copiar mapas para uso..
+Inventory=
+Zooming a map may take several seconds to generate the world, please wait.=
+The zoomed map is now ready.=
+Used to copy, lock, and zoom maps=
+A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and the maximum zoom level may be restricted by server settings to limit the performance impact.=
diff --git a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.ru.tr b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.ru.tr
index bc47ab19f..c2e84b86c 100644
--- a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.ru.tr
+++ b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.ru.tr
@@ -1,4 +1,7 @@
 # textdomain: mcl_cartography_table
 Cartography Table=Картографический стол
-Used to create or copy maps=Используется для создания или копирования карт
-Is used to create or copy maps for use..=Используется для создания или копирования карт.
+Inventory=
+Zooming a map may take several seconds to generate the world, please wait.=
+The zoomed map is now ready.=
+Used to copy, lock, and zoom maps=
+A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and the maximum zoom level may be restricted by server settings to limit the performance impact.=
diff --git a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.zh_CN.tr b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.zh_CN.tr
index 21a835f58..eca2d15d8 100644
--- a/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.zh_CN.tr
+++ b/mods/ITEMS/mcl_cartography_table/locale/mcl_cartography_table.zh_CN.tr
@@ -1,4 +1,7 @@
 # textdomain: mcl_cartography_table
 Cartography Table=制图台
-Used to create or copy maps=用于制作或复制地图。
-Is used to create or copy maps for use..=用于制作或复制供使用的地图。 
+Inventory=
+Zooming a map may take several seconds to generate the world, please wait.=
+The zoomed map is now ready.=
+Used to copy, lock, and zoom maps=
+A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and the maximum zoom level may be restricted by server settings to limit the performance impact.=
diff --git a/mods/ITEMS/mcl_cartography_table/locale/template.txt b/mods/ITEMS/mcl_cartography_table/locale/template.txt
index e85502e31..c8bca82ad 100644
--- a/mods/ITEMS/mcl_cartography_table/locale/template.txt
+++ b/mods/ITEMS/mcl_cartography_table/locale/template.txt
@@ -1,4 +1,7 @@
 # textdomain: mcl_cartography_table
 Cartography Table=
-Used to create or copy maps=
-Is used to create or copy maps for use..=
+Inventory=
+Zooming a map may take several seconds to generate the world, please wait.=
+The zoomed map is now ready.=
+Used to copy, lock, and zoom maps=
+A cartography tables allows to copy, lock, and zoom maps. Locking is not yet useful, and the maximum zoom level may be restricted by server settings to limit the performance impact.=
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.de.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.de.tr
index d7762e512..5761e3dfe 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.de.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.de.tr
@@ -1,8 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=Es kann einen Moment dauern, bis die Karte verfügbar ist.
+The new map is now ready.=Die Karte ist jetzt fertig.
 Empty Map=Leere Karte
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=Leere Karten sind als Karten nicht nützlich, aber sie können gestapelt werden und zu benutzbaren Karten umgewandelt werden.
-Rightclick to create a filled map (which can't be stacked anymore).=Rechtsklick, um die Karte zu füllen. Sie kann dann nicht mehr gestapelt werden.
+Rightclick to create a filled map (which cannot be stacked anymore).=Rechtsklick, um die Karte zu füllen. Sie kann dann nicht mehr gestapelt werden.
 Map=Karte
 Shows a map image.=Zeigt ein Kartenbild.
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=Beim Erstellen speichert die Karte die Gegend in der Nähe als ein Bild, dass jederzeit durch halten der Karte angesehen werden kann.
 Hold the map in your hand. This will display a map on your screen.=Halten Sie die Karte in Ihrer Hand. Eine Karte wird auf Ihrem Bildschirm angezeigt werden.
+Level @1=Stufe @1
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.es.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.es.tr
index d2e502975..30a7d2ccd 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.es.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.es.tr
@@ -1,13 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=Mapa vacio
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=Los mapas vacíos no son útiles como mapas, pero se pueden apilar y convertir en mapas que se pueden usar.
-Rightclick to create a filled map (which can't be stacked anymore).=
+Rightclick to create a filled map (which cannot be stacked anymore).=
 Map=Mapa
 Shows a map image.=
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=
 Hold the map in your hand. This will display a map on your screen.=
-
-
-##### not used anymore #####
-
-Rightclick to start using the map (which can't be stacked anymore).=Haga clic derecho para comenzar a usar el mapa (que ya no se puede apilar).
+Level @1=
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.fr.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.fr.tr
index 1808e839d..b6be2646d 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.fr.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.fr.tr
@@ -1,8 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=Carte Vierge
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=Les cartes vierges ne sont pas utiles en tant que cartes, mais elles peuvent être empilées et transformées en cartes utilisables.
-Rightclick to create a filled map (which can't be stacked anymore).=Clic droit pour créer une carte remplie (qui ne peut plus être empilée).
+Rightclick to create a filled map (which cannot be stacked anymore).=Clic droit pour créer une carte remplie (qui ne peut plus être empilée).
 Map=Carte
 Shows a map image.=Affiche une carte.
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=Lors de sa création, la carte sauvegarde le terrain proche sous forme d'image qui peut être consultée n'importe quand en tenant la carte dans la main.
 Hold the map in your hand. This will display a map on your screen.=Tenez la carte dans votre main. Cela affichera la carte à l'écran.
+Level @1=
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.gl.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.gl.tr
index f8dbf6aff..c4dcc3914 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.gl.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.gl.tr
@@ -1,8 +1,12 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=Mapa baleiro
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=Os mapas baleiros non son útiles como mapas, pero pódense apilar e converter en mapas que se poden usar.
-Rightclick to create a filled map (which can't be stacked anymore).=Fai clic co botón dereito para crear un mapa cheo (que xa non se pode apilar).
+##TODO: fuzzy matched - verify and remove the comment
+Rightclick to create a filled map (which cannot be stacked anymore).=Fai clic co botón dereito para crear un mapa cheo (que xa non se pode apilar).
 Map=Mapa
 Shows a map image.=Mostra unha imaxe de mapa.
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=Cando se crea, o mapa garda a zona próxima como unha imaxe que se pode ver en calquera momento mantendo pulsado o mapa.
 Hold the map in your hand. This will display a map on your screen.=Manteña o mapa na man. Isto amosará un mapa na súa pantalla.
+Level @1=
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.ja.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.ja.tr
index a521f3eba..661612cab 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.ja.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.ja.tr
@@ -1,8 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=白紙の地図
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=白紙の地図は地図として使えませんが、スタックしたり、使える地図に変えられます。
-Rightclick to create a filled map (which can't be stacked anymore).=右クリックで塗りつぶしたマップを作成します(スタックはできなくなります)。
+Rightclick to create a filled map (which cannot be stacked anymore).=右クリックで塗りつぶしたマップを作成します(スタックはできなくなります)。
 Map=地図
 Shows a map image.=地図画像を表示します。
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=作成すると、その付近の地図が画像として保存され、地図をかざすといつでも見ることができます。
 Hold the map in your hand. This will display a map on your screen.=地図を手に持ってください。これで画面に地図が表示されます。
+Level @1=
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.nb.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.nb.tr
index 5c2bc2cbe..ca432833b 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.nb.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.nb.tr
@@ -1,8 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=Tomt kart
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=Tomme kart er ikke nyttige som kart, men de kan stables og gjøres om til kart som kan brukes.
-Rightclick to create a filled map (which can't be stacked anymore).=Høyreklikk for å lage et fylt kart (som ikke kan stables lenger).
+Rightclick to create a filled map (which cannot be stacked anymore).=Høyreklikk for å lage et fylt kart (som ikke kan stables lenger).
 Map=Kart
 Shows a map image.=Viser et kartbilde.
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=Når kartet opprettes, lagres det nærliggende området som et bilde som kan vises når som helst ved å holde kartet.
 Hold the map in your hand. This will display a map on your screen.=Hold kartet i hånden. Dette vil vise et kart på skjermen.
+Level @1=
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.pl.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.pl.tr
index de6ff84d6..c22a3c882 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.pl.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.pl.tr
@@ -1,8 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=Pusta mapa
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=Puste mapy nie są tak użyteczne jak mapy ale mogą być grupowane i zamienione w mapy które już są pożyteczne.
-Rightclick to create a filled map (which can't be stacked anymore).=Kliknij prawy przycisk aby stworzyć wypełnioną mapę (nie będzie już jej można grupować).
+Rightclick to create a filled map (which cannot be stacked anymore).=Kliknij prawy przycisk aby stworzyć wypełnioną mapę (nie będzie już jej można grupować).
 Map=Mapa
 Shows a map image.=Pokazuje obraz mapy.
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=Gdy są utworzone mapy zapisują obszar okolicy jako obrazek, który może być oglądany przez trzymanie mapy w rękach.
 Hold the map in your hand. This will display a map on your screen.=Weź mapę do ręki. To pokaże mapę na twoim ekranie.
+Level @1=
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.pt_BR.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.pt_BR.tr
index a28d211b0..52410da81 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.pt_BR.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.pt_BR.tr
@@ -1,8 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=Mapa Vazio
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=Mapas vazios não são úteis como mapas, mas eles podem ser empilhados e transfomados em mapas aos quais podem ser usados.
-Rightclick to create a filled map (which can't be stacked anymore).=Clique com o botão direito para criar um mapa preenchido (ao qual não pode mais ser empilhado).
+Rightclick to create a filled map (which cannot be stacked anymore).=Clique com o botão direito para criar um mapa preenchido (ao qual não pode mais ser empilhado).
 Map=Mapa
 Shows a map image.=Mostra uma imagem do mapa.
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=Quando criado, o mapa salva a área próxima como uma imagem que pode ser visualizada sempre que você segurar o mapa.
 Hold the map in your hand. This will display a map on your screen.=Segure o mapa em suas mãos. Isso mostrará um mapa em sua tela.
+Level @1=
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.ru.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.ru.tr
index 9c3631f37..8d269abbb 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.ru.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.ru.tr
@@ -1,8 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=Пустая карта
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=Пустые карты не так полезны как карты, но могут складываться в стопки, а также могут быть превращены в полноценные карты.
-Rightclick to create a filled map (which can't be stacked anymore).=Правый клик, чтобы заполнить карту (больше не может складываться в стопки).
+Rightclick to create a filled map (which cannot be stacked anymore).=Правый клик, чтобы заполнить карту (больше не может складываться в стопки).
 Map=Карта
 Shows a map image.=Показать карту.
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=После создания карта сохраняет ближайшую местность которую можно посмотеть в любое время взяв карту в руки.
 Hold the map in your hand. This will display a map on your screen.=Возьмите карту в руки. Так появится изображение карты на экране.
+Level @1=
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.zh_CN.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.zh_CN.tr
index 183c633d0..15a6dc431 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.zh_CN.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.zh_CN.tr
@@ -1,18 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=空白地图
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=空白地图作为地图本身没什么用处,但它们可以堆叠起来,并转化为能使用的地图。
-Rightclick to create a filled map (which can't be stacked anymore).=
+Rightclick to create a filled map (which cannot be stacked anymore).=
 Map=地图
 Shows a map image.=
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=
 Hold the map in your hand. This will display a map on your screen.=
-
-
-##### not used anymore #####
-
-Rightclick to start using the map (which can't be stacked anymore).=右键点击开始使用地图(该地图不能再堆叠了)。
-Maps show your surroundings as you explore the world.=在你探索世界时,地图会显示你周围的环境。
-Hold the map in any of the hotbar slots. This allows you to access the minimap by pressing the minimap key (see controls settings).=将地图放在快捷栏的任意栏位中,这样你按下小地图键就能访问小地图了(见控制设置)。
-In Creative Mode, you don't need this item; the minimap is always available.=在创造模式下,你不需要这个物品,小地图始终是可用的。
-Enables minimap=启用小地图
-Use the minimap key to show the map.=使用小地图键来显示地图。 
+Level @1=
diff --git a/mods/ITEMS/mcl_maps/locale/mcl_maps.zh_TW.tr b/mods/ITEMS/mcl_maps/locale/mcl_maps.zh_TW.tr
index 02386e80d..72bc9f7a9 100644
--- a/mods/ITEMS/mcl_maps/locale/mcl_maps.zh_TW.tr
+++ b/mods/ITEMS/mcl_maps/locale/mcl_maps.zh_TW.tr
@@ -1,18 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=空地圖
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=空的地圖作為地圖是沒有用的,但它們可以被疊加,並變成可以使用的地圖。
-Rightclick to create a filled map (which can't be stacked anymore).=
+Rightclick to create a filled map (which cannot be stacked anymore).=
 Map=地圖
 Shows a map image.=
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=
 Hold the map in your hand. This will display a map on your screen.=
-
-
-##### not used anymore #####
-
-Rightclick to start using the map (which can't be stacked anymore).=右鍵單擊以開始使用地圖(該地圖無法再堆疊)。
-Maps show your surroundings as you explore the world.=當您探索世界時,地圖會顯示您的周圍環境。
-Hold the map in any of the hotbar slots. This allows you to access the minimap by pressing the minimap key (see controls settings).=在任何一個熱鍵槽中放置地圖。這允許你通過按小地圖鍵來訪問小地圖(見控制設置)。
-In Creative Mode, you don't need this item; the minimap is always available.=在創造模式下,您不需要此項目; 小地圖始終可用。
-Enables minimap=啓用小地圖
-Use the minimap key to show the map.=使用小地圖鍵來顯示小地圖。
+Level @1=
diff --git a/mods/ITEMS/mcl_maps/locale/template.txt b/mods/ITEMS/mcl_maps/locale/template.txt
index 27298d2ec..e9f88a540 100644
--- a/mods/ITEMS/mcl_maps/locale/template.txt
+++ b/mods/ITEMS/mcl_maps/locale/template.txt
@@ -1,8 +1,11 @@
 # textdomain: mcl_maps
+It may take a moment for the map to be ready.=
+The new map is now ready.=
 Empty Map=
 Empty maps are not useful as maps, but they can be stacked and turned to maps which can be used.=
-Rightclick to create a filled map (which can't be stacked anymore).=
+Rightclick to create a filled map (which cannot be stacked anymore).=
 Map=
 Shows a map image.=
 When created, the map saves the nearby area as an image that can be viewed any time by holding the map.=
 Hold the map in your hand. This will display a map on your screen.=
+Level @1=

From 405f6760f5b3235faf9488d0694bf4dbd44ba8ee Mon Sep 17 00:00:00 2001
From: kno10 <erich.schubert@gmail.com>
Date: Sun, 19 Jan 2025 21:11:30 +0100
Subject: [PATCH 5/5] use get_node_name for zooming

---
 mods/ITEMS/mcl_maps/init.lua | 187 +++++++++++++++++------------------
 1 file changed, 88 insertions(+), 99 deletions(-)

diff --git a/mods/ITEMS/mcl_maps/init.lua b/mods/ITEMS/mcl_maps/init.lua
index 885becb03..6844c4477 100644
--- a/mods/ITEMS/mcl_maps/init.lua
+++ b/mods/ITEMS/mcl_maps/init.lua
@@ -1,13 +1,13 @@
--- TODO: improve support for larger zoom levels, maybe by NOT using vmanip but rather raycasting?
+-- TODO: improve support for larger zoom levels, benchmark raycasting, too?
 -- TODO: only send texture to players that have the map
 -- TODO: use ephemeral textures or base64 inline textures to eventually allow explorer maps?
 -- TODO: show multiple players on the map
 -- TODO: show banners on map
+-- TODO: when the minimum supported Luanti version has core.get_node_raw, use it
 -- Check for engine updates that allow improvements
 mcl_maps = {}
 
--- level 4 already may take some 20 minutes...
-mcl_maps.max_zoom = (tonumber(core.settings:get("vl_maps_max_zoom")) or 2)
+mcl_maps.max_zoom = (tonumber(core.settings:get("vl_maps_max_zoom")) or 4)
 mcl_maps.enable_maps = core.settings:get_bool("enable_real_maps", true)
 mcl_maps.allow_nether_maps = core.settings:get_bool("vl_maps_allow_nether", true)
 mcl_maps.map_allow_overlap = core.settings:get_bool("vl_maps_allow_overlap", true) -- 50% overlap allowed in each level
@@ -27,15 +27,16 @@ local string_to_pos = core.string_to_pos
 local get_item_group = core.get_item_group
 local dynamic_add_media = core.dynamic_add_media
 local get_connected_players = core.get_connected_players
+local get_node_name_raw = mcl_vars.get_node_name_raw
 
 local storage = core.get_mod_storage()
 local worldpath = core.get_worldpath()
-local map_textures_path = worldpath .. "/mcl_maps/"
+local map_textures_path = worldpath .. DIR_DELIM .. "mcl_maps" .. DIR_DELIM
 
 core.mkdir(map_textures_path)
 
 local function load_json_file(name)
-	local file = assert(io.open(modpath .. "/" .. name .. ".json", "r"))
+	local file = assert(io.open(modpath .. DIR_DELIM .. name .. ".json", "r"))
 	local data = core.parse_json(file:read("*all"))
 	file:close()
 	return data
@@ -45,108 +46,96 @@ local texture_colors = load_json_file("colors")
 
 local maps_generating, maps_loading = {}, {}
 
-local c_air = core.get_content_id("air")
+-- Main map generation function, called from emerge
+local function do_generate_map(id, minp, maxp, callback, t1)
+	local t2 = os.clock()
+	-- Generate a (usually) 128x128 linear array for the image
+	local pixels = {}
+	local xsize, zsize = maxp.x - minp.x + 1, maxp.z - minp.z + 1
+	-- Step size, for zoom levels > 1
+	local xstep, zstep = ceil(xsize / 128), ceil(zsize / 128)
+	for z = zsize, 1, -zstep do
+		local map_z = minp.z + z - 1
+		local last_height
+		for x = 1, xsize, xstep do
+			local map_x = minp.x + x - 1
+			-- color aggregate and height information (for 3D effect)
+			local cagg, height = { 0, 0, 0, 0 }, nil
+			local solid_under_air = -1 -- anything but air, actually
+			for map_y = maxp.y, minp.y, -1 do
+				local nodename, _, param2 = get_node_name_raw(map_x, map_y, map_z)
+				if nodename ~= "air" then
+					local color = texture_colors[nodename]
+					-- use param2 if available:
+					if color and type(color[1]) == "table" then
+						color = color[param2 + 1] or color[1]
+					end
+					if color then
+						if solid_under_air == 0 then
+							cagg, height = { 0, 0, 0, 0 }, nil -- reset
+							solid_under_air = 1
+						end
+						local alpha = cagg[4] -- 0 (transparent) to 255 (opaque)
+						if alpha < 255 then
+							local a = (color[4] or 255) * (255 - alpha) / 255 -- 0 to 255
+							local f = a / 255 -- 0 to 1, color contribution
+							-- Alpha blend the colors:
+							cagg[1] = cagg[1] + f * color[1]
+							cagg[2] = cagg[2] + f * color[2]
+							cagg[3] = cagg[3] + f * color[3]
+							alpha = cagg[4] + a -- new alpha, 0 to 255
+							cagg[4] = alpha
+						end
 
-local function generate_map(id, minp, maxp, callback)
+						-- ground estimate with transparent blocks
+						if alpha > 140 and not height then height = map_y end
+						if alpha >= 250 and solid_under_air > 0 then
+							-- adjust color to give a 3d effect
+							if last_height and height then
+								local dheight = max(-48, min((height - last_height) * 8, 48))
+								cagg[1] = cagg[1] + dheight
+								cagg[2] = cagg[2] + dheight
+								cagg[3] = cagg[3] + dheight
+							end
+							cagg[4] = 255 -- make fully opaque
+							break
+						end
+					end
+				elseif solid_under_air == -1 then
+					solid_under_air = 0 -- first air
+				end
+			end
+			-- clamp colors values to 0:255 for PNG
+			-- because 3d height effect may exceed this range
+			cagg[1] = max(0, min(round(cagg[1]), 255))
+			cagg[2] = max(0, min(round(cagg[2]), 255))
+			cagg[3] = max(0, min(round(cagg[3]), 255))
+			cagg[4] = max(0, min(round(cagg[4]), 255))
+			pixels[#pixels + 1] = string.char(cagg[1], cagg[2], cagg[3], cagg[4])
+			last_height = height
+		end
+	end
+	-- Save as png texture
+	local t3 = os.clock()
+	local filename = map_textures_path .. "mcl_maps_map_" .. id .. ".png"
+	core.safe_file_write(filename, core.encode_png(xsize / xstep, zsize / zstep, table.concat(pixels)))
+	local t4 = os.clock()
+	--core.log("action", string.format("Completed map %s after %.2fms (%.2fms emerge, %.2fms map, %.2fms png)", id, (os.clock()-t1)*1000, (t2-t1)*1000, (t3-t2)*1000, (t4-t3)*1000))
+	maps_generating[id] = nil
+	if callback then callback(id, filename) end
+end
+
+-- Trigger map generation
+local function emerge_generate_map(id, minp, maxp, callback)
 	if maps_generating[id] then return end
 	maps_generating[id] = true
-	-- FIXME: reduce resolution when zoomed out
 	local t1 = os.clock()
 	core.emerge_area(minp, maxp, function(blockpos, action, calls_remaining)
 		if calls_remaining > 0 then return end
 		-- do a DOUBLE emerge to give mapgen the chance to place structures triggered by the initial emerge
 		core.emerge_area(minp, maxp, function(blockpos, action, calls_remaining)
 			if calls_remaining > 0 then return end
-
-			-- Load voxelmanip, measure time as this is fairly expensive
-			local t2 = os.clock()
-			local vm = core.get_voxel_manip()
-			local emin, emax = vm:read_from_map(minp, maxp)
-			local data = vm:get_data()
-			local param2data = vm:get_param2_data()
-			local t3 = os.clock()
-
-			-- Generate a (usually) 128x128 linear array for the image
-			local pixels = {}
-			local area = VoxelArea:new({ MinEdge = emin, MaxEdge = emax })
-			local xsize, zsize = maxp.x - minp.x + 1, maxp.z - minp.z + 1
-			-- Step size, for zoom levels > 1
-			local xstep, zstep = ceil(xsize / 128), ceil(zsize / 128)
-			local ystride = area.ystride
-			for z = zsize, 1, -zstep do
-				local map_z = minp.z  + z - 1
-				local last_height
-				for x = 1, xsize, xstep do
-					local map_x = minp.x + x - 1
-
-					-- color aggregate and height information (for 3D effect)
-					local cagg, height = { 0, 0, 0, 0 }, nil
-					local solid_under_air = -1 -- anything but air, actually
-					local index = area:index(map_x, maxp.y, map_z) + ystride
-					for map_y = maxp.y, minp.y, -1 do
-						index = index - ystride -- vertically down until we are opaque
-
-						local c_id = data[index]
-						if c_id ~= c_air then
-							local color = texture_colors[core.get_name_from_content_id(c_id)]
-							-- use param2 if available:
-							if color and type(color[1]) == "table" then
-								color = color[param2data[index] + 1] or color[1]
-							end
-							if color then
-								if solid_under_air == 0 then
-									cagg, height = { 0, 0, 0, 0 }, nil -- reset
-									solid_under_air = 1
-								end
-								local alpha = cagg[4] -- 0 (transparent) to 255 (opaque)
-								if alpha < 255 then
-									local a = (color[4] or 255) * (255 - alpha) / 255 -- 0 to 255
-									local f = a / 255 -- 0 to 1, color contribution
-									-- Alpha blend the colors:
-									cagg[1] = cagg[1] + f * color[1]
-									cagg[2] = cagg[2] + f * color[2]
-									cagg[3] = cagg[3] + f * color[3]
-									alpha = cagg[4] + a -- new alpha, 0 to 255
-									cagg[4] = alpha
-								end
-
-								-- ground estimate with transparent blocks
-								if alpha > 140 and not height then height = map_y end
-								if alpha >= 250 and solid_under_air > 0 then
-									-- adjust color to give a 3d effect
-									if last_height and height then
-										local dheight = max(-48, min((height - last_height) * 8, 48))
-										cagg[1] = cagg[1] + dheight
-										cagg[2] = cagg[2] + dheight
-										cagg[3] = cagg[3] + dheight
-									end
-									cagg[4] = 255 -- make fully opaque
-									break
-								end
-							end
-						elseif solid_under_air == -1 then
-							solid_under_air = 0 -- first air
-						end
-					end
-					-- clamp colors values to 0:255 for PNG
-					-- because 3d height effect may exceed this range
-					cagg[1] = max(0, min(round(cagg[1]), 255))
-					cagg[2] = max(0, min(round(cagg[2]), 255))
-					cagg[3] = max(0, min(round(cagg[3]), 255))
-					cagg[4] = max(0, min(round(cagg[4]), 255))
-					pixels[#pixels + 1] = string.char(cagg[1], cagg[2], cagg[3], cagg[4])
-					last_height = height
-				end
-			end
-			-- Save as png texture
-			local filename = map_textures_path .. "mcl_maps_map_" .. id .. ".png"
-			local data = core.encode_png(xsize / xstep, zsize / zstep, table.concat(pixels))
-			local f = assert(io.open(filename, "wb"))
-			f:write(data)
-			f:close()
-			-- core.log("action", string.format("Completed map %s after %.2fms (%.2fms emerge, %.2fms LVM, %.2fms map)", id, (os.clock()-t1)*1000, (t2-t1)*1000, (t3-t2)*1000, (os.clock()-t3)*1000))
-			maps_generating[id] = nil
-			if callback then callback(id, filename) end
+			do_generate_map(id, minp, maxp, callback, t1)
 		end)
 	end)
 end
@@ -197,7 +186,7 @@ local function configure_map(itemstack, cx, dim, cz, zoom, callback)
 	meta:set_string("mcl_maps:maxp", pos_to_string(maxp))
 	tt.reload_itemstack_description(itemstack)
 
-	generate_map(id, minp, maxp, callback)
+	emerge_generate_map(id, minp, maxp, callback)
 	return itemstack
 end