mirror of
https://git.minetest.land/VoxeLibre/VoxeLibre.git
synced 2025-04-21 14:55:15 +02:00
Zoomable maps, but limited to 1 level for now.
This commit is contained in:
parent
64907ead0d
commit
88ca202647
5 changed files with 417 additions and 259 deletions
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue