398 lines
11 KiB
Lua
398 lines
11 KiB
Lua
--
|
|
-- Minetest advmarkers mod
|
|
--
|
|
-- © 2019 by luk3yx
|
|
--
|
|
|
|
advmarkers = {
|
|
dated_death_markers = false,
|
|
}
|
|
|
|
local DEFAULT_COLOUR = 0xbf360c
|
|
|
|
-- Get the mod storage
|
|
local storage = minetest.get_mod_storage()
|
|
local hud = {}
|
|
advmarkers.last_coords = {}
|
|
|
|
local abs, type = math.abs, type
|
|
local vector_copy = vector.copy or vector.new
|
|
|
|
local round = math.round or function(n)
|
|
return math.floor(n + 0.5)
|
|
end
|
|
|
|
-- Convert positions to/from strings
|
|
local function pos_to_string(pos)
|
|
if type(pos) == 'table' then
|
|
pos = minetest.pos_to_string(vector.round(pos))
|
|
end
|
|
if type(pos) == 'string' then
|
|
return pos
|
|
end
|
|
end
|
|
|
|
local function dir_ok(n)
|
|
return type(n) == 'number' and abs(n) < 31000
|
|
end
|
|
local function string_to_pos(pos)
|
|
if type(pos) == 'string' then
|
|
pos = minetest.string_to_pos(pos)
|
|
end
|
|
if type(pos) == 'table' and dir_ok(pos.x) and dir_ok(pos.y) and
|
|
dir_ok(pos.z) then
|
|
-- Create a normal table so that adding additional keys (like "colour")
|
|
-- is guaranteed to work correctly.
|
|
return {x = round(pos.x), y = round(pos.y), z = round(pos.z)}
|
|
end
|
|
end
|
|
|
|
local get_player_by_name = minetest.get_player_by_name
|
|
local get_connected_players = minetest.get_connected_players
|
|
if minetest.get_modpath('cloaking') then
|
|
get_player_by_name = cloaking.get_player_by_name
|
|
get_connected_players = cloaking.get_connected_players
|
|
end
|
|
|
|
-- Set the HUD position
|
|
function advmarkers.set_hud_pos(player, pos, title, colour)
|
|
local name = player:get_player_name()
|
|
pos = string_to_pos(pos)
|
|
if not pos then return end
|
|
if not title then
|
|
title = pos.x .. ', ' .. pos.y .. ', ' .. pos.z
|
|
end
|
|
colour = colour or DEFAULT_COLOUR
|
|
if hud[name] then
|
|
player:hud_change(hud[name], 'name', title)
|
|
player:hud_change(hud[name], 'world_pos', pos)
|
|
player:hud_change(hud[name], 'number', colour)
|
|
else
|
|
hud[name] = player:hud_add({
|
|
hud_elem_type = 'waypoint',
|
|
name = title,
|
|
text = 'm',
|
|
number = colour,
|
|
world_pos = pos
|
|
})
|
|
end
|
|
minetest.chat_send_player(name, 'Waypoint set to ' ..
|
|
minetest.colorize(("#%06x"):format(colour), title))
|
|
return true
|
|
end
|
|
|
|
function advmarkers.is_waypoint_shown(player, wp_name)
|
|
local pname = player:get_player_name()
|
|
local hud_elem = hud[pname] and player:hud_get(hud[pname])
|
|
if hud_elem and hud_elem.name == wp_name then
|
|
local pos = advmarkers.get_waypoint(player, wp_name)
|
|
if pos then
|
|
return vector.equals(hud_elem.world_pos, pos)
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function advmarkers.clear_hud(player)
|
|
local name = player:get_player_name()
|
|
if hud[name] then
|
|
player:hud_remove(hud[name])
|
|
hud[name] = nil
|
|
return true
|
|
end
|
|
end
|
|
|
|
-- Get and save player storage
|
|
local storage_cache = {}
|
|
local function get_storage(player)
|
|
local pname = player:get_player_name()
|
|
if storage_cache[pname] then
|
|
return storage_cache[pname]
|
|
end
|
|
|
|
local raw = player:get_meta():get_string('advmarkers:waypoints') or ''
|
|
local version = raw:sub(1, 1)
|
|
local res
|
|
if version == '0' then
|
|
-- Player meta: 0{"Marker name": {"x": 1, "y": 2, "z": 3}}
|
|
res = minetest.parse_json(raw:sub(2))
|
|
elseif version == '' then
|
|
-- Mod storage: return {["marker-Marker name"] = "(1,2,3)"}
|
|
res = {}
|
|
raw = minetest.deserialize(storage:get_string(pname))
|
|
if raw then
|
|
for name, pos in pairs(raw) do
|
|
if name:sub(1, 7) == 'marker-' then
|
|
res[name:sub(8)] = string_to_pos(pos)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Cache the waypoint list
|
|
res = res or {}
|
|
storage_cache[pname] = res
|
|
return res
|
|
end
|
|
|
|
local function save_storage(player, markers)
|
|
local name = player:get_player_name()
|
|
local meta = player:get_meta()
|
|
if next(markers) then
|
|
meta:set_string('advmarkers:waypoints', '0' ..
|
|
minetest.write_json(markers))
|
|
else
|
|
meta:set_string('advmarkers:waypoints', '')
|
|
end
|
|
storage:set_string(name, '')
|
|
|
|
return true
|
|
end
|
|
|
|
-- Add a waypoint
|
|
function advmarkers.set_waypoint(player, pos, name, colour)
|
|
pos = string_to_pos(pos)
|
|
if not pos then
|
|
return false, "Invalid position!"
|
|
end
|
|
|
|
name = tostring(name)
|
|
if name == "" then
|
|
return false, "No name specified!"
|
|
end
|
|
|
|
if #name > 100 then
|
|
return false, "Waypoint name too long!"
|
|
end
|
|
|
|
local data = get_storage(player)
|
|
|
|
local count = 0
|
|
for _ in pairs(data) do count = count + 1 end
|
|
if count >= 100 then
|
|
return false, "You have too many waypoints!"
|
|
end
|
|
|
|
-- Add the colour (string_to_pos makes a copy of pos)
|
|
pos.colour = colour ~= DEFAULT_COLOUR and colour or nil
|
|
|
|
data[name] = pos
|
|
return save_storage(player, data)
|
|
end
|
|
|
|
-- Delete a waypoint
|
|
function advmarkers.delete_waypoint(player, name)
|
|
local data = get_storage(player)
|
|
data[name] = nil
|
|
return save_storage(player, data)
|
|
end
|
|
|
|
-- Get a waypoint
|
|
function advmarkers.get_waypoint(player, name)
|
|
local pos = get_storage(player)[name]
|
|
return pos and vector_copy(pos), pos and pos.colour or DEFAULT_COLOUR
|
|
end
|
|
|
|
-- Rename a waypoint
|
|
function advmarkers.rename_waypoint(player, oldname, newname, colour)
|
|
if oldname == newname and not colour then return true end
|
|
|
|
local data = get_storage(player)
|
|
if data[newname] and oldname ~= newname then return end
|
|
local wp = data[oldname]
|
|
if wp and colour then
|
|
wp.colour = colour ~= DEFAULT_COLOUR and colour or nil
|
|
end
|
|
data[newname], data[oldname] = wp, nil
|
|
return save_storage(player, data)
|
|
end
|
|
|
|
-- Get waypoint names
|
|
function advmarkers.get_waypoint_names(player, sorted)
|
|
local data = get_storage(player)
|
|
local res = {}
|
|
for name in pairs(data) do
|
|
res[#res + 1] = name
|
|
end
|
|
if sorted or sorted == nil then table.sort(res) end
|
|
return res
|
|
end
|
|
|
|
-- Display a waypoint
|
|
function advmarkers.display_waypoint(player, name)
|
|
local pos, colour = advmarkers.get_waypoint(player, name)
|
|
return advmarkers.set_hud_pos(player, pos, name, colour)
|
|
end
|
|
|
|
-- Export waypoints
|
|
function advmarkers.export(player, raw)
|
|
local s = {}
|
|
for name, pos in pairs(get_storage(player)) do
|
|
s['marker-' .. name] = pos_to_string(pos)
|
|
end
|
|
|
|
if not raw then
|
|
s = minetest.compress(minetest.write_json(s))
|
|
s = 'J' .. minetest.encode_base64(s)
|
|
end
|
|
return s
|
|
end
|
|
|
|
-- Import waypoints
|
|
function advmarkers.import(player, s)
|
|
if type(s) ~= 'table' then
|
|
if s:sub(1, 1) ~= 'J' then return end
|
|
s = minetest.decode_base64(s:sub(2))
|
|
local success, msg = pcall(minetest.decompress, s)
|
|
if not success then return end
|
|
s = minetest.parse_json(msg)
|
|
if type(s) ~= 'table' then return end
|
|
end
|
|
|
|
local data = get_storage(player)
|
|
|
|
-- Limit the total number of waypoints
|
|
local count = 0
|
|
for _ in pairs(data) do count = count + 1 end
|
|
|
|
-- Parse the export table
|
|
for field, pos in pairs(s) do
|
|
if type(field) == 'string' and type(pos) == 'string' and
|
|
field:sub(1, 7) == 'marker-' then
|
|
pos = string_to_pos(pos)
|
|
if pos and #field <= 107 then
|
|
-- Prevent collisions
|
|
local name = field:sub(8)
|
|
local c = 0
|
|
while data[name] and not vector.equals(data[name], pos) and
|
|
c < 50 do
|
|
name = name .. '_'
|
|
c = c + 1
|
|
end
|
|
|
|
-- Sanity check
|
|
if c < 50 then
|
|
data[name] = string_to_pos(pos)
|
|
end
|
|
|
|
count = count + 1
|
|
if count >= 100 then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return save_storage(player, data)
|
|
end
|
|
|
|
-- Get waypoint position
|
|
function advmarkers.get_chatcommand_pos(player, pos)
|
|
local pname = player:get_player_name()
|
|
|
|
-- Validate the position
|
|
if pos == 'h' or pos == 'here' then
|
|
pos = player:get_pos()
|
|
elseif pos == 't' or pos == 'there' then
|
|
if not advmarkers.last_coords[pname] then
|
|
return false, 'No "there" position found!'
|
|
end
|
|
pos = advmarkers.last_coords[pname]
|
|
else
|
|
pos = string_to_pos(pos)
|
|
if not pos then
|
|
return false, 'Invalid position!'
|
|
end
|
|
end
|
|
return pos
|
|
end
|
|
|
|
-- Find co-ordinates sent in chat messages
|
|
local function get_coords(msg)
|
|
if msg:byte(1) == 1 or #msg > 1000 then return end
|
|
local pos = msg:match('%-?[0-9%.]+, *%-?[0-9%.]+, *%-?[0-9%.]+')
|
|
if pos then
|
|
return string_to_pos(pos)
|
|
end
|
|
end
|
|
|
|
-- Get global co-ords
|
|
minetest.register_on_chat_message(function(_, msg)
|
|
if msg:sub(1, 1) == '/' then return end
|
|
local pos = get_coords(msg)
|
|
if pos then
|
|
advmarkers.last_coords = {}
|
|
for _, player in ipairs(get_connected_players()) do
|
|
advmarkers.last_coords[player:get_player_name()] = pos
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Override chat_send_player to get PMed co-ords etc
|
|
local old_chat_send_player = minetest.chat_send_player
|
|
function minetest.chat_send_player(name, msg, ...)
|
|
if type(name) == 'string' and type(msg) == 'string' and
|
|
get_player_by_name(name) then
|
|
local pos = get_coords(msg)
|
|
if pos then
|
|
advmarkers.last_coords[name] = pos
|
|
end
|
|
end
|
|
return old_chat_send_player(name, msg, ...)
|
|
end
|
|
|
|
-- Clean up variables if a player leaves
|
|
minetest.register_on_leaveplayer(function(player)
|
|
local name = player:get_player_name()
|
|
hud[name] = nil
|
|
storage_cache[name] = nil
|
|
advmarkers.last_coords[name] = nil
|
|
end)
|
|
|
|
-- Auto-add waypoints on death.
|
|
minetest.register_on_dieplayer(function(player)
|
|
local name
|
|
if advmarkers.dated_death_markers then
|
|
name = os.date('Death on %Y-%m-%d %H:%M:%S')
|
|
else
|
|
name = 'Death waypoint'
|
|
end
|
|
local pos = player:get_pos()
|
|
advmarkers.last_coords[player] = pos
|
|
advmarkers.set_waypoint(player, pos, name)
|
|
minetest.chat_send_player(player:get_player_name(),
|
|
'Added waypoint "' .. name .. '".')
|
|
end)
|
|
|
|
-- Load other files
|
|
local modpath = minetest.get_modpath("advmarkers")
|
|
dofile(modpath .. "/chatcommands.lua")
|
|
dofile(modpath .. "/gui.lua")
|
|
|
|
-- Backwards compatibility
|
|
for _, func_name in ipairs({"set_hud_pos", "set_waypoint", "delete_waypoint",
|
|
"get_waypoint", "rename_waypoint", "get_waypoint_names", "display_waypoint",
|
|
"export", "import", "display_formspec", "get_chatcommand_pos"}) do
|
|
local func = assert(advmarkers[func_name])
|
|
local function wrapper(player, ...)
|
|
if type(player) == "string" then
|
|
minetest.log("warning", "[advmarkers] Calling advmarkers." ..
|
|
func_name .. "() with a player name is deprecated.")
|
|
player = get_player_by_name(player)
|
|
if not player then return end
|
|
end
|
|
return func(player, ...)
|
|
end
|
|
|
|
if func_name:find("waypoint", 1, true) then
|
|
local old_name = func_name:gsub('waypoint', 'marker')
|
|
advmarkers[old_name] = function(...)
|
|
minetest.log("warning", "[advmarkers] advmarkers." .. old_name ..
|
|
"() is deprecated, use advmarkers." .. func_name ..
|
|
"() instead.")
|
|
return wrapper(...)
|
|
end
|
|
end
|
|
|
|
advmarkers[func_name] = wrapper
|
|
end
|