-- -- Minetest advmarkers mod -- -- The advmarkers CSM ported to a server-side mod -- advmarkers = { dated_death_markers = false } -- Get the mod storage local storage = minetest.get_mod_storage() local hud = {} advmarkers.last_coords = {} -- 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 string_to_pos(pos) if type(pos) == 'string' then pos = minetest.string_to_pos(pos) end if type(pos) == 'table' then return vector.round(pos) end end -- Get player name or object 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 local function get_player(player, t) local name if type(player) == 'string' then name = player if t ~= 0 then player = get_player_by_name(name) end else name = player:get_player_name() end if t == 0 then return name elseif t == 1 then return player end return name, player end -- Set the HUD position function advmarkers.set_hud_pos(player, pos, title) local name, player = get_player(player) pos = string_to_pos(pos) if not player or not pos then return end if not title then title = pos.x .. ', ' .. pos.y .. ', ' .. pos.z end if hud[player] then player:hud_change(hud[player], 'name', title) player:hud_change(hud[player], 'world_pos', pos) else hud[player] = player:hud_add({ hud_elem_type = 'waypoint', name = title, text = 'm', number = 0xbf360c, world_pos = pos }) end minetest.chat_send_player(name, 'Waypoint set to ' .. title) return true end -- Get and save player storage local function get_storage(name) name = get_player(name, 0) return minetest.deserialize(storage:get_string(name)) or {} end local function save_storage(name, data) name = get_player(name, 0) if type(data) == 'table' then data = minetest.serialize(data) end if type(data) ~= 'string' then return end if #data > 0 then storage:set_string(name, data) else storage:set_string(name, '') end return true end -- Add a waypoint function advmarkers.set_waypoint(player, pos, name) pos = pos_to_string(pos) if not pos then return end local data = get_storage(player) data['marker-' .. tostring(name)] = pos return save_storage(player, data) end advmarkers.set_marker = advmarkers.set_waypoint -- Delete a waypoint function advmarkers.delete_waypoint(player, name) local data = get_storage(player) data['marker-' .. tostring(name)] = nil return save_storage(player, data) end advmarkers.delete_marker = advmarkers.delete_waypoint -- Get a waypoint function advmarkers.get_waypoint(player, name) local data = get_storage(player) return string_to_pos(data['marker-' .. tostring(name)]) end advmarkers.get_marker = advmarkers.get_waypoint -- Rename a waypoint and re-interpret the position. function advmarkers.rename_waypoint(player, oldname, newname) player = get_player(player, 0) oldname, newname = tostring(oldname), tostring(newname) local pos = advmarkers.get_waypoint(player, oldname) if not pos or not advmarkers.set_waypoint(player, pos, newname) then return end if oldname ~= newname then advmarkers.delete_waypoint(player, oldname) end return true end advmarkers.rename_marker = advmarkers.rename_waypoint -- Get waypoint names function advmarkers.get_waypoint_names(name, sorted) local data = get_storage(name) local res = {} for name, pos in pairs(data) do if name:sub(1, 7) == 'marker-' then table.insert(res, name:sub(8)) end end if sorted or sorted == nil then table.sort(res) end return res end advmarkers.get_marker_names = advmarkers.get_waypoint_names -- Display a waypoint function advmarkers.display_waypoint(player, name) return advmarkers.set_hud_pos(player, advmarkers.get_waypoint(player, name), name) end advmarkers.display_marker = advmarkers.display_waypoint -- Export waypoints function advmarkers.export(player, raw) local s = get_storage(player) if raw == 'M' then s = minetest.compress(minetest.serialize(s)) s = 'M' .. minetest.encode_base64(s) elseif not raw then s = minetest.compress(minetest.write_json(s)) s = 'J' .. minetest.encode_base64(s) end return s end -- Import waypoints - Note that this won't import strings made by older -- versions of the CSM. 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) end -- Iterate over waypoints to preserve existing ones and check for errors. if type(s) == 'table' then local data = get_storage(player) for name, pos in pairs(s) do if type(name) == 'string' and type(pos) == 'string' and name:sub(1, 7) == 'marker-' and minetest.string_to_pos(pos) and data[name] ~= pos then -- Prevent collisions local c = 0 while data[name] and c < 50 do name = name .. '_' c = c + 1 end -- Sanity check if c < 50 then data[name] = pos end end end return save_storage(player, data) end end -- Get the waypoints formspec local formspec_list = {} local selected_name = {} function advmarkers.display_formspec(player) player = get_player(player, 0) if not get_player_by_name(player) then return end local formspec = 'size[5.25,8]' .. 'label[0,0;Waypoint list]' .. 'button_exit[0,7.5;1.3125,0.5;display;Display]' .. 'button[1.3125,7.5;1.3125,0.5;teleport;Teleport]' .. 'button[2.625,7.5;1.3125,0.5;rename;Rename]' .. 'button[3.9375,7.5;1.3125,0.5;delete;Delete]' .. 'textlist[0,0.75;5,6;marker;' -- Iterate over all the waypoints local selected = 1 formspec_list[player] = advmarkers.get_waypoint_names(player) for id, name in ipairs(formspec_list[player]) do if id > 1 then formspec = formspec .. ',' end if not selected_name[player] then selected_name[player] = name end if name == selected_name[player] then selected = id end formspec = formspec .. '##' .. minetest.formspec_escape(name) end -- Close the text list and display the selected waypoint position formspec = formspec .. ';' .. tostring(selected) .. ']' if selected_name[player] then local pos = advmarkers.get_waypoint(player, selected_name[player]) if pos then pos = minetest.formspec_escape(tostring(pos.x) .. ', ' .. tostring(pos.y) .. ', ' .. tostring(pos.z)) pos = 'Waypoint position: ' .. pos formspec = formspec .. 'label[0,6.75;' .. pos .. ']' end else -- Draw over the buttons formspec = formspec .. 'button_exit[0,7.5;5.25,0.5;quit;Close dialog]' .. 'label[0,6.75;No waypoints. Add one with "/add_wp".]' end -- Display the formspec return minetest.show_formspec(player, 'advmarkers-ssm', formspec) end -- Get waypoint position function advmarkers.get_chatcommand_pos(player, pos) local pname = get_player(player, 0) -- Validate the position if pos == 'h' or pos == 'here' then pos = get_player(player, 1):get_pos() elseif pos == 't' or pos == 'there' then if not advmarkers.last_coords[pname] then return false, 'No-one has used ".coords" and you have not died!' 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 local function register_chatcommand_alias(old, ...) local def = assert(minetest.registered_chatcommands[old]) def.name = nil for i = 1, select('#', ...) do minetest.register_chatcommand(select(i, ...), table.copy(def)) end end -- Open the waypoints GUI local csm_key = string.char(1) .. 'ADVMARKERS_SSCSM' .. string.char(1) minetest.register_chatcommand('mrkr', { params = '', description = 'Open the advmarkers GUI', func = function(pname, param) if param:sub(1, #csm_key) == csm_key then -- SSCSM communication param = param:sub(#csm_key + 1) local cmd = param:sub(1, 1) if cmd == 'D' then -- D: Delete advmarkers.delete_waypoint(pname, param:sub(2)) elseif cmd == 'S' then -- S: Set local s, e = param:find(' ') if s and e then local pos = string_to_pos(param:sub(2, s - 1)) if pos then advmarkers.set_waypoint(pname, pos, param:sub(e + 1)) end end elseif cmd == '0' then -- 0: Display if not advmarkers.display_waypoint(pname, param:sub(2)) then minetest.chat_send_player(pname, 'Error displaying waypoint!') end end minetest.chat_send_player(pname, csm_key .. advmarkers.export(pname)) elseif param == '' then advmarkers.display_formspec(pname) else local pos, err = advmarkers.get_chatcommand_pos(pname, param) if not pos then return false, err end if not advmarkers.set_hud_pos(pname, pos) then return false, 'Error setting the waypoint!' end end end }) register_chatcommand_alias('mrkr', 'wp', 'wps', 'waypoint', 'waypoints') -- Add a waypoint minetest.register_chatcommand('add_mrkr', { params = ' ', description = 'Adds a waypoint.', func = function(pname, param) -- Get the parameters local s, e = param:find(' ') if not s or not e then return false, 'Invalid syntax! See /help add_mrkr for more info.' end local pos = param:sub(1, s - 1) local name = param:sub(e + 1) -- Get the position local pos, err = advmarkers.get_chatcommand_pos(pname, pos) if not pos then return false, err end -- Validate the name if not name or #name < 1 then return false, 'Invalid name!' end -- Set the waypoint return advmarkers.set_waypoint(pname, pos, name), 'Done!' end }) register_chatcommand_alias('add_mrkr', 'add_wp', 'add_waypoint') -- Set the HUD minetest.register_on_player_receive_fields(function(player, formname, fields) local pname, player = get_player(player) if formname == 'advmarkers-ignore' then return true elseif formname ~= 'advmarkers-ssm' then return end local name = false if fields.marker then local event = minetest.explode_textlist_event(fields.marker) if event.index then name = formspec_list[pname][event.index] end else name = selected_name[pname] end if name then if fields.display then if not advmarkers.display_waypoint(player, name) then minetest.chat_send_player(pname, 'Error displaying waypoint!') end elseif fields.rename then minetest.show_formspec(pname, 'advmarkers-ssm', 'size[6,3]' .. 'label[0.35,0.2;Rename waypoint]' .. 'field[0.3,1.3;6,1;new_name;New name;' .. minetest.formspec_escape(name) .. ']' .. 'button[0,2;3,1;cancel;Cancel]' .. 'button[3,2;3,1;rename_confirm;Rename]') elseif fields.rename_confirm then if fields.new_name and #fields.new_name > 0 then if advmarkers.rename_waypoint(pname, name, fields.new_name) then selected_name[pname] = fields.new_name else minetest.chat_send_player(pname, 'Error renaming waypoint!') end advmarkers.display_formspec(pname) else minetest.chat_send_player(pname, 'Please enter a new name for the waypoint.' ) end elseif fields.teleport then minetest.show_formspec(pname, 'advmarkers-ssm', 'size[6,2.2]' .. 'label[0.35,0.25;' .. minetest.formspec_escape( 'Teleport to a waypoint\n - ' .. name ) .. ']' .. 'button[0,1.25;3,1;cancel;Cancel]' .. 'button_exit[3,1.25;3,1;teleport_confirm;Teleport]') elseif fields.teleport_confirm then -- Teleport with /teleport local pos = advmarkers.get_waypoint(pname, name) if not pos then minetest.chat_send_player(pname, 'Error teleporting to waypoint!') elseif minetest.check_player_privs(pname, 'teleport') then player:set_pos(pos) minetest.chat_send_player(pname, 'Teleported to waypoint "' .. name .. '".') else minetest.chat_send_player(pname, 'Insufficient privileges!') end elseif fields.delete then minetest.show_formspec(pname, 'advmarkers-ssm', 'size[6,2]' .. 'label[0.35,0.25;Are you sure you want to delete this waypoint?]' .. 'button[0,1;3,1;cancel;Cancel]' .. 'button[3,1;3,1;delete_confirm;Delete]') elseif fields.delete_confirm then advmarkers.delete_waypoint(pname, name) selected_name[pname] = nil advmarkers.display_formspec(pname) elseif fields.cancel then advmarkers.display_formspec(pname) elseif name ~= selected_name[pname] then selected_name[pname] = name if not fields.quit then advmarkers.display_formspec(pname) end end elseif fields.display or fields.delete then minetest.chat_send_player(pname, 'Please select a waypoint.') end return true 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) -- Allow string exporting minetest.register_chatcommand('mrkr_export', { params = '', description = 'Exports an advmarkers string containing all your waypoints.', func = function(name, param) local export if param == 'old' then export = advmarkers.export(name, 'M') else export = advmarkers.export(name) end minetest.show_formspec(name, 'advmarkers-ignore', 'field[_;Your waypoint export string;' .. minetest.formspec_escape(export) .. ']') end }) register_chatcommand_alias('mrkr_export', 'wp_export', 'waypoint_export') -- String importing minetest.register_chatcommand('mrkr_import', { params = '', description = 'Imports an advmarkers string. This will not overwrite ' .. 'existing waypoints that have the same name.', func = function(name, param) if advmarkers.import(name, param) then return true, 'Waypoints imported!' else return false, 'Invalid advmarkers string!' end end }) register_chatcommand_alias('mrkr_export', 'wp_import', 'waypoint_import') -- Chat channels .coords integration. -- You do not need to have chat channels installed for this to work. local function get_coords(msg, strict) local s = 'Current Position: %-?[0-9]+, %-?[0-9]+, %-?[0-9]+%.' if strict then s = '^' .. s end local s, e = msg:find(s) local pos = false if s and e then pos = string_to_pos(msg:sub(s + 18, e - 1)) end return pos end -- Get global co-ords table.insert(minetest.registered_on_chat_messages, 1, function(name, msg) if msg:sub(1, 1) == '/' then return end local pos = get_coords(msg, true) 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 = get_player(player, 0) hud[name] = nil formspec_list[name] = nil selected_name[name] = nil advmarkers.last_coords[name] = nil end) -- Add '/mrkrthere' minetest.register_chatcommand('mrkrthere', { params = '', description = 'Alias for "/mrkr there".', func = function(name, param) return minetest.registered_chatcommands['mrkr'].func(name, 'there') end }) -- SSCSM support if minetest.global_exists('sscsm') and sscsm.register then sscsm.register({ name = 'advmarkers', file = minetest.get_modpath('advmarkers') .. '/sscsm.lua', }) end