-- -- Minetest advmarkers CSM -- -- Needs the https://github.com/Billy-S/kingdoms_game/tree/master/mods/marker -- mod to be able to display HUD elements -- advmarkers = {} -- Get the mod storage local storage = minetest.get_mod_storage() -- 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 -- Set the HUD position -- TODO: Make this entirely client-side or allow the command to be changed. function advmarkers.set_hud_pos(pos) pos = string_to_pos(pos) if not pos then return end minetest.run_server_chatcommand('mrkr', tostring(pos.x) .. ' ' .. tostring(pos.y) .. ' ' .. tostring(pos.z)) return true end -- Add a marker function advmarkers.set_marker(pos, name) pos = pos_to_string(pos) if not pos then return end storage:set_string('marker-' .. tostring(name), pos) return true end -- Delete a marker function advmarkers.delete_marker(name) storage:set_string('marker-' .. tostring(name), '') end -- Get a marker function advmarkers.get_marker(name) return string_to_pos(storage:get_string('marker-' .. tostring(name))) end -- Rename a marker and re-interpret the position. function advmarkers.rename_marker(oldname, newname) oldname, newname = tostring(oldname), tostring(newname) local pos = advmarkers.get_marker(oldname) if not pos or not advmarkers.set_marker(pos, newname) then return end if oldname ~= newname then advmarkers.delete_marker(oldname) end return true end -- Display a marker function advmarkers.display_marker(name) return advmarkers.set_hud_pos(advmarkers.get_marker(name)) end -- Export markers function advmarkers.export(raw) local s = storage:to_table().fields if not raw then s = minetest.compress(minetest.serialize(s)) s = 'M' .. minetest.encode_base64(s) end return s end -- Import markers function advmarkers.import(s) if type(s) ~= 'table' then if s:sub(1, 1) ~= 'M' then return false, 'No M' end s = minetest.decode_base64(s:sub(2)) local success, msg = pcall(minetest.decompress, s) if not success then return end s = minetest.deserialize(msg) end -- Iterate over markers to preserve existing ones and check for errors. if type(s) == 'table' then 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 storage:get_string(name) ~= pos then -- Prevent collisions local c = 0 while #storage:get_string(name) > 0 and c < 50 do name = name .. '_' c = c + 1 end -- Sanity check if c < 50 then storage:set_string(name, pos) end end end return true end end -- Get the markers formspec local formspec_list = {} local selected_name = false function advmarkers.display_formspec() local formspec = 'size[5.25,8]' .. 'label[0,0;Marker 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 markers local id = 0 local selected = 1 formspec_list = {} for name, pos in pairs(storage:to_table().fields) do if name:sub(1, 7) == 'marker-' then id = id + 1 if id > 1 then formspec = formspec .. ',' end name = name:sub(8) if not selected_name then selected_name = name end if name == selected_name then selected = id end formspec_list[#formspec_list + 1] = name formspec = formspec .. '##' .. minetest.formspec_escape(name) end end -- Close the text list and display the selected marker position formspec = formspec .. ';' .. tostring(selected) .. ']' if selected_name then local pos = advmarkers.get_marker(selected_name) if pos then pos = minetest.formspec_escape(tostring(pos.x) .. ', ' .. tostring(pos.y) .. ', ' .. tostring(pos.z)) pos = 'Marker 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 markers. Add one with ".add_mrkr".]' end -- Display the formspec return minetest.show_formspec('advmarkers-csm', formspec) end -- Open the markers GUI minetest.register_chatcommand('mrkr', { params = '', description = 'Open the advmarkers GUI', func = advmarkers.display_formspec }) -- Add a marker minetest.register_chatcommand('add_mrkr', { params = ' ', description = 'Adds a marker.', func = function(param) 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) -- Validate the position if pos == 'here' then pos = minetest.localplayer:get_pos() elseif pos == 'there' then if not advmarkers.last_coords then return false, 'No-one has used ".coords" and you have not died!' end pos = advmarkers.last_coords else pos = string_to_pos(pos) if not pos then return false, 'Invalid position!' end end -- Validate the name if not name or #name < 1 then return false, 'Invalid name!' end -- Set the marker return advmarkers.set_marker(pos, name), 'Done!' end }) -- Set the HUD minetest.register_on_formspec_input(function(formname, fields) if formname == 'advmarkers-ignore' then return true elseif formname ~= 'advmarkers-csm' 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[event.index] end else name = selected_name end if name then if fields.display then if not advmarkers.display_marker(name) then minetest.display_chat_message('Error displaying marker!') end elseif fields.rename then minetest.show_formspec('advmarkers-csm', 'size[6,3]' .. 'label[0.35,0.2;Rename marker]' .. '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_marker(name, fields.new_name) then selected_name = fields.new_name else minetest.display_chat_message('Error renaming marker!') end advmarkers.display_formspec() else minetest.display_chat_message( 'Please enter a new name for the marker.' ) end elseif fields.teleport then minetest.show_formspec('advmarkers-csm', 'size[6,2.2]' .. 'label[0.35,0.25;' .. minetest.formspec_escape( 'Teleport to a marker\n - ' .. name ) .. ']' .. 'button[0,1.25;2,1;cancel;Cancel]' .. 'button_exit[2,1.25;1,1;teleport_tpj;/tpj]' .. 'button_exit[3,1.25;1,1;teleport_tpc;/tpc]' .. 'button_exit[4,1.25;2,1;teleport_confirm;/teleport]') elseif fields.teleport_tpj then -- Teleport with /tpj local pos = advmarkers.get_marker(name) if pos and minetest.localplayer then local cpos = minetest.localplayer:get_pos() for _, dir in ipairs({'x', 'y', 'z'}) do local distance = pos[dir] - cpos[dir] minetest.run_server_chatcommand('tpj', dir .. ' ' .. tostring(distance)) end else minetest.display_chat_message('Error teleporting to marker!') end elseif fields.teleport_confirm or fields.teleport_tpc then -- Teleport with /teleport local pos = advmarkers.get_marker(name) local cmd if fields.teleport_confirm then cmd = 'teleport' else cmd = 'tpc' end if pos and minetest.localplayer then minetest.run_server_chatcommand(cmd, pos.x .. ',' .. pos.y .. ',' .. pos.z) else minetest.display_chat_message('Error teleporting to marker!') end elseif fields.delete then minetest.show_formspec('advmarkers-csm', 'size[6,2]' .. 'label[0.35,0.25;Are you sure you want to delete this marker?]' .. 'button[0,1;3,1;cancel;Cancel]' .. 'button[3,1;3,1;delete_confirm;Delete]') elseif fields.delete_confirm then advmarkers.delete_marker(name) selected_name = false advmarkers.display_formspec() elseif fields.cancel then advmarkers.display_formspec() elseif name ~= selected_name then selected_name = name advmarkers.display_formspec() end elseif fields.display or fields.delete then minetest.display_chat_message('Please select a marker.') end return true end) -- Auto-add markers on death. minetest.register_on_death(function() if minetest.localplayer then local name = os.date('Death on %Y-%m-%d %H:%M:%S') local pos = minetest.localplayer:get_pos() advmarkers.last_coords = pos advmarkers.set_marker(pos, name) minetest.display_chat_message('Added marker "' .. name .. '".') end end) -- Allow string exporting minetest.register_chatcommand('mrkr_export', { params = '', description = 'Exports an advmarkers string containing all your markers.', func = function(param) minetest.show_formspec('advmarkers-ignore', 'field[_;Your marker export string;' .. minetest.formspec_escape(advmarkers.export()) .. ']') end }) -- String importing minetest.register_chatcommand('mrkr_import', { params = '', description = 'Imports an advmarkers string. This will not overwrite ' .. 'existing markers that have the same name.', func = function(param) if advmarkers.import(param) then return true, 'Markers imported!' else return false, 'Invalid advmarkers string!' end end }) -- Chat channels .coords integration. -- You do not need to have chat channels installed for this to work. if not minetest.registered_on_receiving_chat_message then minetest.registered_on_receiving_chat_message = minetest.registered_on_receiving_chat_messages end table.insert(minetest.registered_on_receiving_chat_message, 1, function(msg) local s, e = msg:find('Current Position: %-?[0-9]+, %-?[0-9]+, %-?[0-9]+%.') if s and e then local pos = string_to_pos(msg:sub(s + 18, e - 1)) if pos then advmarkers.last_coords = pos end end end) -- Add '.mrkrthere' minetest.register_chatcommand('mrkrthere', { params = '', description = 'Adds a (temporary) marker at the last ".coords" position.', func = function(param) if not advmarkers.last_coords then return false, 'No-one has used ".coords" and you have not died!' elseif not advmarkers.set_hud_pos(advmarkers.last_coords) then return false, 'Error setting the marker!' end end })