An advanced-ish waypoints mod for Minetest.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

297 lines
9.8KB

  1. --
  2. -- Minetest advmarkers SSCSM
  3. --
  4. -- Copyright © 2019 by luk3yx
  5. -- License: https://git.minetest.land/luk3yx/advmarkers/src/branch/master/LICENSE.md
  6. --
  7. advmarkers = {}
  8. local data = {}
  9. assert(not sscsm.restrictions or not sscsm.restrictions.chat_messages,
  10. 'The advmarkers SSCSM needs to be able to send chat messages!')
  11. -- Convert positions to/from strings
  12. local function pos_to_string(pos)
  13. if type(pos) == 'table' then
  14. pos = minetest.pos_to_string(vector.round(pos))
  15. end
  16. if type(pos) == 'string' then
  17. return pos
  18. end
  19. end
  20. local function string_to_pos(pos)
  21. if type(pos) == 'string' then
  22. pos = minetest.string_to_pos(pos)
  23. end
  24. if type(pos) == 'table' then
  25. return vector.round(pos)
  26. end
  27. end
  28. -- Run remote command
  29. -- This is easier to do with chatcommands because servers cannot send mod
  30. -- channel messages to specific clients.
  31. local csm_key = string.char(1) .. 'ADVMARKERS_SSCSM' .. string.char(1)
  32. local function run_remote_command(cmd, param)
  33. local msg = csm_key
  34. if cmd then
  35. msg = msg .. cmd
  36. if param then msg = msg .. param end
  37. end
  38. minetest.run_server_chatcommand('wp', msg)
  39. end
  40. -- Display a waypoint
  41. function advmarkers.display_waypoint(name)
  42. name = tostring(name)
  43. if data['marker-' .. name] then
  44. run_remote_command('0', tostring(name))
  45. return true
  46. else
  47. return false
  48. end
  49. end
  50. -- Get a waypoint
  51. function advmarkers.get_waypoint(name)
  52. return string_to_pos(data['marker-' .. tostring(name)])
  53. end
  54. advmarkers.get_marker = advmarkers.get_waypoint
  55. -- Delete a waypoint
  56. function advmarkers.delete_waypoint(name)
  57. name = tostring(name)
  58. if data['marker-' .. name] ~= nil then
  59. data['marker-' .. name] = nil
  60. run_remote_command('D', name)
  61. end
  62. end
  63. advmarkers.delete_marker = advmarkers.delete_waypoint
  64. -- Set a waypoint
  65. function advmarkers.set_waypoint(pos, name)
  66. pos = pos_to_string(pos)
  67. if not pos then return end
  68. name = tostring(name)
  69. data['marker-' .. name] = pos
  70. run_remote_command('S', pos:gsub(' ', '') .. ' ' .. name)
  71. return true
  72. end
  73. advmarkers.set_marker = advmarkers.set_waypoint
  74. -- Rename a waypoint and re-interpret the position.
  75. function advmarkers.rename_waypoint(oldname, newname)
  76. oldname, newname = tostring(oldname), tostring(newname)
  77. local pos = advmarkers.get_waypoint(oldname)
  78. if not pos or not advmarkers.set_waypoint(pos, newname) then return end
  79. if oldname ~= newname then
  80. advmarkers.delete_waypoint(oldname)
  81. end
  82. return true
  83. end
  84. advmarkers.rename_marker = advmarkers.rename_waypoint
  85. -- Import waypoints - Note that this won't import strings made by older
  86. -- versions of the CSM.
  87. function advmarkers.import(s, clear)
  88. if type(s) ~= 'table' then
  89. if s:sub(1, 1) ~= 'J' then return end
  90. s = minetest.decode_base64(s:sub(2))
  91. local success, msg = pcall(minetest.decompress, s)
  92. if not success then return end
  93. s = minetest.parse_json(msg)
  94. end
  95. -- Iterate over waypoints to preserve existing ones and check for errors.
  96. if type(s) ~= 'table' then return end
  97. if clear then data = {} end
  98. for name, pos in pairs(s) do
  99. if type(name) == 'string' and type(pos) == 'string' and
  100. name:sub(1, 7) == 'marker-' and minetest.string_to_pos(pos) and
  101. data[name] ~= pos then
  102. -- Prevent collisions
  103. local c = 0
  104. while data[name] and c < 50 do
  105. name = name .. '_'
  106. c = c + 1
  107. end
  108. -- Sanity check
  109. if c < 50 then
  110. data[name] = pos
  111. end
  112. end
  113. end
  114. end
  115. -- Get waypoint names
  116. function advmarkers.get_waypoint_names(sorted)
  117. local res = {}
  118. for name, pos in pairs(data) do
  119. if name:sub(1, 7) == 'marker-' then
  120. table.insert(res, name:sub(8))
  121. end
  122. end
  123. if sorted or sorted == nil then table.sort(res) end
  124. return res
  125. end
  126. -- Display the formspec
  127. local formspec_list = {}
  128. local selected_name = false
  129. function advmarkers.display_formspec()
  130. local formspec = 'size[5.25,8]' ..
  131. 'label[0,0;Waypoint list ' ..
  132. minetest.colorize('#888888', '(SSCSM)') .. ']' ..
  133. 'button_exit[0,7.5;1.3125,0.5;display;Display]' ..
  134. 'button[1.3125,7.5;1.3125,0.5;teleport;Teleport]' ..
  135. 'button[2.625,7.5;1.3125,0.5;rename;Rename]' ..
  136. 'button[3.9375,7.5;1.3125,0.5;delete;Delete]' ..
  137. 'textlist[0,0.75;5,6;marker;'
  138. -- Iterate over all the markers
  139. local id = 0
  140. local selected = 1
  141. formspec_list = advmarkers.get_waypoint_names()
  142. for id, name in ipairs(formspec_list) do
  143. if id > 1 then formspec = formspec .. ',' end
  144. if not selected_name then selected_name = name end
  145. if name == selected_name then selected = id end
  146. formspec = formspec .. '##' .. minetest.formspec_escape(name)
  147. end
  148. -- Close the text list and display the selected marker position
  149. formspec = formspec .. ';' .. tostring(selected) .. ']'
  150. if selected_name then
  151. local pos = advmarkers.get_marker(selected_name)
  152. if pos then
  153. pos = minetest.formspec_escape(tostring(pos.x) .. ', ' ..
  154. tostring(pos.y) .. ', ' .. tostring(pos.z))
  155. pos = 'Waypoint position: ' .. pos
  156. formspec = formspec .. 'label[0,6.75;' .. pos .. ']'
  157. end
  158. else
  159. -- Draw over the buttons
  160. formspec = formspec .. 'button_exit[0,7.5;5.25,0.5;quit;Close dialog]' ..
  161. 'label[0,6.75;No waypoints. Add one with "/add_wp".]'
  162. end
  163. -- Display the formspec
  164. return minetest.show_formspec('advmarkers-sscsm', formspec)
  165. end
  166. -- Register chatcommands
  167. local mrkr_cmd
  168. function mrkr_cmd(param)
  169. if param == '' then return advmarkers.display_formspec() end
  170. if param == '--ssm' then param = '' end
  171. minetest.run_server_chatcommand('wp', param)
  172. end
  173. sscsm.register_chatcommand('mrkr', mrkr_cmd)
  174. sscsm.register_chatcommand('wp', mrkr_cmd)
  175. sscsm.register_chatcommand('wps', mrkr_cmd)
  176. sscsm.register_chatcommand('waypoint', mrkr_cmd)
  177. sscsm.register_chatcommand('waypoints', mrkr_cmd)
  178. function mrkr_cmd(param)
  179. minetest.run_server_chatcommand('add_wp', param)
  180. run_remote_command()
  181. end
  182. sscsm.register_chatcommand('add_wp', mrkr_cmd)
  183. sscsm.register_chatcommand('add_waypoint', mrkr_cmd)
  184. sscsm.register_chatcommand('add_mrkr', mrkr_cmd)
  185. mrkr_cmd = nil
  186. -- Set the HUD
  187. minetest.register_on_formspec_input(function(formname, fields)
  188. if formname == 'advmarkers-ignore' then
  189. return true
  190. elseif formname ~= 'advmarkers-sscsm' then
  191. return
  192. end
  193. local name = false
  194. if fields.marker then
  195. local event = minetest.explode_textlist_event(fields.marker)
  196. if event.index then
  197. name = formspec_list[event.index]
  198. end
  199. else
  200. name = selected_name
  201. end
  202. if name then
  203. if fields.display then
  204. if not advmarkers.display_waypoint(name) then
  205. minetest.display_chat_message('Error displaying waypoint!')
  206. end
  207. elseif fields.rename then
  208. minetest.show_formspec('advmarkers-sscsm', 'size[6,3]' ..
  209. 'label[0.35,0.2;Rename waypoint]' ..
  210. 'field[0.3,1.3;6,1;new_name;New name;' ..
  211. minetest.formspec_escape(name) .. ']' ..
  212. 'button[0,2;3,1;cancel;Cancel]' ..
  213. 'button[3,2;3,1;rename_confirm;Rename]')
  214. elseif fields.rename_confirm then
  215. if fields.new_name and #fields.new_name > 0 then
  216. if advmarkers.rename_waypoint(name, fields.new_name) then
  217. selected_name = fields.new_name
  218. else
  219. minetest.display_chat_message('Error renaming waypoint!')
  220. end
  221. advmarkers.display_formspec()
  222. else
  223. minetest.display_chat_message(
  224. 'Please enter a new name for the waypoint.'
  225. )
  226. end
  227. elseif fields.teleport then
  228. minetest.show_formspec('advmarkers-sscsm', 'size[6,2.2]' ..
  229. 'label[0.35,0.25;' .. minetest.formspec_escape(
  230. 'Teleport to a waypoint\n - ' .. name
  231. ) .. ']' ..
  232. 'button[0,1.25;3,1;cancel;Cancel]' ..
  233. 'button_exit[3,1.25;3,1;teleport_confirm;Teleport]')
  234. elseif fields.teleport_confirm then
  235. -- Teleport with /teleport
  236. local pos = advmarkers.get_waypoint(name)
  237. if pos then
  238. minetest.run_server_chatcommand('teleport',
  239. pos.x .. ', ' .. pos.y .. ', ' .. pos.z)
  240. else
  241. minetest.display_chat_message('Error teleporting to waypoint!')
  242. end
  243. elseif fields.delete then
  244. minetest.show_formspec('advmarkers-sscsm', 'size[6,2]' ..
  245. 'label[0.35,0.25;Are you sure you want to delete this marker?]' ..
  246. 'button[0,1;3,1;cancel;Cancel]' ..
  247. 'button[3,1;3,1;delete_confirm;Delete]')
  248. elseif fields.delete_confirm then
  249. advmarkers.delete_waypoint(name)
  250. selected_name = false
  251. advmarkers.display_formspec()
  252. elseif fields.cancel then
  253. advmarkers.display_formspec()
  254. elseif name ~= selected_name then
  255. selected_name = name
  256. advmarkers.display_formspec()
  257. end
  258. elseif fields.display or fields.delete then
  259. minetest.display_chat_message('Please select a marker.')
  260. end
  261. return true
  262. end)
  263. -- Update the waypoint list
  264. minetest.register_on_receiving_chat_message(function(message)
  265. if message:sub(1, #csm_key) == csm_key then
  266. advmarkers.import(message:sub(#csm_key + 1), true)
  267. return true
  268. end
  269. end)
  270. run_remote_command()