Zoomable maps, but limited to 1 level for now.

This commit is contained in:
kno10 2024-12-07 13:33:15 +01:00 committed by teknomunk
parent 64907ead0d
commit 88ca202647
5 changed files with 417 additions and 259 deletions
mods/ITEMS
mcl_cartography_table
mcl_maps
settingtypes.txt

View file

@ -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,

View file

@ -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.
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.

View file

@ -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

View file

@ -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

View file

@ -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