Add wieldable maps (like mcl_maps)

This commit is contained in:
Nils Dagsson Moskopp 2022-05-20 00:37:15 +02:00
parent 67c75b061c
commit ead238c376
Signed by: erle
GPG Key ID: A3BC671C35191080
6 changed files with 398 additions and 130 deletions

View File

@ -25,3 +25,25 @@ local pixels = {
{ K, K, K, K, K, K, _ },
}
tga_encoder.image(pixels):save("textures/maps_arrow_diagonal.tga")
local pixels = {
{ _, _, K, K, K, _, _ },
{ _, K, W, W, W, K, _ },
{ K, W, W, W, W, W, K },
{ K, W, W, W, W, W, K },
{ K, W, W, W, W, W, K },
{ _, K, W, W, W, K, _ },
{ _, _, K, K, K, _, _ },
}
tga_encoder.image(pixels):save("textures/maps_dot_large.tga")
local pixels = {
{ _, _, _, _, _, _, _ },
{ _, _, K, K, K, _, _ },
{ _, K, W, W, W, K, _ },
{ _, K, W, W, W, K, _ },
{ _, K, W, W, W, K, _ },
{ _, _, K, K, K, _, _ },
{ _, _, _, _, _, _, _ },
}
tga_encoder.image(pixels):save("textures/maps_dot_small.tga")

505
init.lua
View File

@ -47,14 +47,17 @@ function tga_encoder.image:blit_icon(icon, pos, stop_colors)
end
maps = {}
maps.dots = {}
maps.maps = {}
maps.minp = {}
maps.maxp = {}
maps.work = {}
maps.sent = {}
maps.posx = {}
maps.posz = {}
maps.dark = {} -- key: player name; value: is it dark?
maps.huds = {} -- key: player name; value: player huds
maps.maps = {} -- key: player name; value: map texture
maps.mark = {} -- key: player name; value: marker texture
maps.marx = {} -- key: player name; value: marker x offset
maps.mary = {} -- key: player name; value: marker y offset
maps.load = {} -- maps loaded by players
maps.sent = {} -- maps sent to players
maps.work = {} -- maps being created
local size = 80
@ -62,41 +65,29 @@ local worldpath = minetest.get_worldpath()
local textures_dir = worldpath .. "/maps/"
minetest.mkdir(textures_dir)
maps.create_map = function(pos, player_name)
maps.get_map_filename = function(map_id)
return "maps_map_texture_" .. map_id .. ".tga"
end
maps.create_map = function(pos)
local itemstack = ItemStack("maps:map")
local meta = itemstack:get_meta()
local map_id = tostring(os.time() + math.random())
meta:set_string("maps:id", map_id)
local minp = vector.multiply(vector.floor(vector.divide(pos, size)), size)
meta:set_string("maps:minp", minetest.pos_to_string(minp))
local maxp = vector.add(minp, vector.new(size - 1, size - 1, size - 1))
meta:set_string("maps:maxp", minetest.pos_to_string(maxp))
local prefix, _ = minetest.pos_to_string(maxp)
prefix, _ = prefix:gsub("%(", "")
prefix, _ = prefix:gsub("%)", "")
prefix, _ = prefix:gsub(",", "_")
local filename = prefix .. ".tga"
if maps.work[filename] then
return
end
local player = minetest.get_player_by_name(player_name)
if maps.sent[filename] then
maps.minp[player_name] = minp
maps.maxp[player_name] = maxp
player:hud_change(
maps.maps[player_name],
"text",
filename
)
return
end
local xpos = vector.round(pos)
meta:set_string("maps:xpos", minetest.pos_to_string(xpos))
local filename = maps.get_map_filename(map_id)
maps.work[filename] = true
player:hud_change(
maps.maps[player_name],
"text",
"blank.png"
)
local emerge_callback = function(
blockpos,
action,
@ -377,22 +368,7 @@ maps.create_map = function(pos, player_name)
filepath,
{ colormap=colormap }
)
minetest.dynamic_add_media(
filepath,
function()
local player =minetest.get_player_by_name(player_name)
player:hud_change(
maps.maps[player_name],
"text",
filename
)
maps.minp[player_name] = minp
maps.maxp[player_name] = maxp
maps.sent[filename] = true
maps.work[filename] = false
end
)
maps.work[filename] = false
end
minetest.emerge_area(
@ -400,6 +376,165 @@ maps.create_map = function(pos, player_name)
maxp,
emerge_callback
)
return itemstack
end
maps.load_map = function(map_id)
assert( nil ~= map_id )
if (
"" == map_id or
maps.work[map_id]
) then
return
end
local filename = maps.get_map_filename(map_id)
if not maps.sent[map_id] then
if not minetest.features.dynamic_add_media_table then
-- minetest.dynamic_add_media() blocks in
-- Minetest 5.3 and 5.4 until media loads
minetest.dynamic_add_media(
textures_dir .. filename,
function() end
)
maps.load[map_id] = true
else
-- minetest.dynamic_add_media() never blocks
-- in Minetest 5.5, callback runs after load
minetest.dynamic_add_media(
textures_dir .. filename,
function()
maps.load[map_id] = true
end
)
end
maps.sent[map_id] = true
end
if maps.load[map_id] then
return filename
end
end
maps.encode_map_item_meta = function(input)
return minetest.encode_base64(
minetest.compress(
input,
"deflate",
9
)
)
end
maps.decode_map_item_meta = function(input)
return minetest.decompress(
minetest.decode_base64(input),
"deflate",
9
)
end
result_original = "foo\0\01\02\x03\n\rbar"
result_roundtrip = maps.decode_map_item_meta(
maps.encode_map_item_meta(result_original)
)
assert(
result_original == result_roundtrip,
"maps: mismatch between maps.encode_map_item_meta() and maps.decode_map_item_meta()"
)
maps.load_map_item = function(itemstack)
local meta = itemstack:get_meta()
local map_id = meta:get_string("maps:id")
if (
not map_id or
"" == map_id or
maps.work[map_id]
) then
return
end
if maps.load[map_id] then
return maps.load_map(map_id)
end
local texture_file_name = maps.get_map_filename(map_id)
local texture_file_path = textures_dir .. texture_file_name
-- does the texture file exist?
local texture_file_handle_read = io.open(
texture_file_path,
"rb"
)
local texture_file_exists = true
local texture_data_from_file
if nil == texture_file_handle_read then
texture_file_exists = false
else
texture_data_from_file = texture_file_handle_read:read("*a")
texture_file_handle_read:close()
end
-- does the texture item meta exist?
local tga_deflate_base64 = meta:get_string("maps:tga_deflate_base64")
local texture_item_meta_exists = true
if "" == tga_deflate_base64 then
texture_item_meta_exists = false
end
if texture_file_exists and nil ~= texture_data_from_file then
if texture_item_meta_exists then
-- sanity check: do we have the same textures?
-- if server-side texture has changed, take it
if maps.decode_map_item_meta(tga_deflate_base64) ~= texture_data_from_file then
minetest.log(
"action",
"maps: update item meta from file content for map " .. map_id
)
meta:set_string(
"maps:tga_deflate_base64",
maps.encode_map_item_meta(texture_data_from_file)
)
end
else
-- map items without meta should not exist, so
-- we now write the file contents to item meta
minetest.log(
"action",
"maps: create item meta from file content for map " .. map_id
)
meta:set_string(
"maps:tga_deflate_base64",
maps.encode_map_item_meta(texture_data_from_file)
)
end
else
-- no texture file → could be a world download
-- so we look for missing texture in item meta
-- and write that to the map texture file here
if texture_item_meta_exists then
minetest.log(
"action",
"maps: create file content from item meta for map " .. map_id
)
assert(
minetest.safe_file_write(
texture_file_path,
decode_item_meta(tga_deflate_base64)
)
)
else
minetest.log(
"error",
"no data for map " .. map_id
)
return
end
end
return maps.load_map(map_id)
end
minetest.register_on_joinplayer(
@ -413,14 +548,171 @@ minetest.register_on_joinplayer(
offset = { x = 0, y = 0 },
scale = { x = 4, y = 4 }
}
local dot_def = table.copy(map_def)
maps.maps[player_name] = player:hud_add(map_def)
maps.dots[player_name] = player:hud_add(dot_def)
maps.minp[player_name] = { x=-32000, y=0, z=0 }
maps.maxp[player_name] = { x=-32000, y=0, z=0 }
local pos_def = table.copy(map_def)
maps.huds[player_name] = {
map = player:hud_add(map_def),
pos = player:hud_add(pos_def),
}
end
)
maps.show_map_hud = function(player)
local wield_item = player:get_wielded_item()
local texture = maps.load_map_item(wield_item)
local player_pos = player:get_pos()
local player_name = player:get_player_name()
if not player_pos or not texture then
if maps.maps[player_name] then
player:hud_change(
maps.huds[player_name].map,
"text",
"blank.png"
)
player:hud_change(
maps.huds[player_name].pos,
"text",
"blank.png"
)
maps.maps[player_name] = nil
end
return
end
local pos = vector.round(player_pos)
local meta = wield_item:get_meta()
local meta_minp = meta:get_string("maps:minp")
assert( "" ~= meta_minp )
local minp = minetest.string_to_pos(meta_minp)
local meta_maxp = meta:get_string("maps:maxp")
assert( "" ~= meta_maxp )
local maxp = minetest.string_to_pos(meta_maxp)
local meta_xpos = meta:get_string("maps:xpos")
if "" ~= meta_xpos then
local xpos = minetest.string_to_pos(meta_xpos)
local x_x = xpos.x - minp.x - 4
local x_z = maxp.z - xpos.z - 4
local x_overlay = "^[combine:" ..
size .. "x" .. size .. ":" ..
x_x .. "," .. x_z .. "=maps_x.tga"
texture = texture .. x_overlay
end
local light_level = minetest.get_node_light(pos) or 0
local darkness = 255 - (light_level * 17)
local light_level_overlay = "^[colorize:black:" .. darkness
if (
texture ~= maps.maps[player_name] or
darkness ~= maps.dark[player_name]
) then
player:hud_change(
maps.huds[player_name].map,
"text",
texture .. light_level_overlay
)
maps.dark[player_name] = darkness
maps.maps[player_name] = texture
end
local marker
local dot_large = "maps_dot_large.tga" .. "^[makealpha:1,1,1"
local dot_small = "maps_dot_small.tga" .. "^[makealpha:1,1,1"
if pos.x < minp.x then
if minp.x - pos.x < size then
marker = dot_large
else
marker = dot_small
end
pos.x = minp.x
elseif pos.x > maxp.x then
if pos.x - maxp.x < size then
marker = dot_large
else
marker = dot_small
end
pos.x = maxp.x
end
-- we never override the small marker
-- yes, this is a literal corner case
if pos.z < minp.z then
if minp.z - pos.z < 256 and marker ~= dot_small then
marker = dot_large
else
marker = dot_small
end
pos.z = minp.z
elseif pos.z > maxp.z then
if pos.z - maxp.z < 256 and marker ~= dot_small then
marker = dot_large
else
marker = dot_small
end
pos.z = maxp.z
end
if nil == marker then
local yaw = (
math.floor(
player:get_look_horizontal()
* 180 / math.pi / 45 + 0.5
) % 8
) * 45
if (
yaw == 0 or
yaw == 90 or
yaw == 180 or
yaw == 270
) then
marker = "maps_arrow.tga" ..
"^[makealpha:1,1,1" ..
"^[transformR" ..
yaw
elseif (
yaw == 45 or
yaw == 135 or
yaw == 225 or
yaw == 315
) then
marker = "maps_arrow_diagonal.tga" ..
"^[makealpha:1,1,1" ..
"^[transformR" ..
(yaw - 45)
end
end
if marker and marker ~= maps.mark[player_name] then
player:hud_change(
maps.huds[player_name].pos,
"text",
marker
)
maps.mark[player_name] = marker
end
local marker_x = (pos.x - minp.x - (size/2)) * 4
local marker_y = (maxp.z - pos.z - size + 3) * 4
if (
marker_x ~= maps.marx[player_name] or
marker_y ~= maps.mary[player_name]
) then
player:hud_change(
maps.huds[player_name].pos,
"offset",
{
x = marker_x,
y = marker_y,
}
)
maps.marx[player_name] = marker_x
maps.mary[player_name] = marker_y
end
end
local time_elapsed = 0
minetest.register_globalstep(
@ -431,78 +723,31 @@ minetest.register_globalstep(
end
local players = minetest.get_connected_players()
for _, player in pairs(players) do
local pos = vector.round(player:get_pos())
local player_name = player:get_player_name()
if (
pos.x == maps.posx[player_name] and
pos.z == maps.posz[player_name]
) then
-- continue
else
local minp = maps.minp[player_name]
local maxp = maps.maxp[player_name]
if pos.x < minp.x then
maps.create_map(pos, player_name)
elseif pos.x > maxp.x then
maps.create_map(pos, player_name)
end
if pos.z < minp.z then
maps.create_map(pos, player_name)
elseif pos.z > maxp.z then
maps.create_map(pos, player_name)
end
local x = (pos.x - minp.x - (size/2)) * 4
local y = (minp.z - pos.z) * 4 - 2
player:hud_change(
maps.dots[player_name],
"offset",
{
x = x,
y = y,
}
)
maps.posx[player_name] = pos.x
maps.posz[player_name] = pos.z
end
local marker
local yaw = (
math.floor(
player:get_look_horizontal()
* 180 / math.pi / 45 + 0.5
) % 8
) * 45
if (
yaw == 0 or
yaw == 90 or
yaw == 180 or
yaw == 270
) then
marker = "maps_arrow.tga" ..
"^[makealpha:1,1,1" ..
"^[transformR" ..
yaw
elseif (
yaw == 45 or
yaw == 135 or
yaw == 225 or
yaw == 315
) then
marker = "maps_arrow_diagonal.tga" ..
"^[makealpha:1,1,1" ..
"^[transformR" ..
(yaw - 45)
end
if marker then
player:hud_change(
maps.dots[player_name],
"text",
marker
)
end
maps.show_map_hud(player)
end
end
)
maps.create_map_item = function(itemstack, player, pointed_thing)
local pos = player:get_pos()
if pos then
local map = maps.create_map(pos)
return map
end
end
minetest.override_item(
"map:mapping_kit",
{
on_place = maps.create_map_item,
on_secondary_use = maps.create_map_item,
}
)
minetest.register_craftitem(
"maps:map",
{
description = "Map",
inventory_image = "maps_map.tga"
}
)

View File

@ -1,3 +1,4 @@
depends = tga_encoder
description = Shows maps in the player HUD
name = maps
depends = map

BIN
textures/maps_dot_large.tga Normal file

Binary file not shown.

BIN
textures/maps_dot_small.tga Normal file

Binary file not shown.

BIN
textures/maps_map.tga Normal file

Binary file not shown.