The CSM version of advmarkers.
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.

496 lines
16KB

  1. --
  2. -- Minetest advmarkers CSM
  3. --
  4. -- Needs the https://github.com/Billy-S/kingdoms_game/tree/master/mods/marker
  5. -- mod to be able to display HUD elements
  6. --
  7. advmarkers = {}
  8. -- Get the mod storage
  9. local storage = minetest.get_mod_storage()
  10. -- Convert positions to/from strings
  11. local function pos_to_string(pos)
  12. if type(pos) == 'table' then
  13. pos = minetest.pos_to_string(vector.round(pos))
  14. end
  15. if type(pos) == 'string' then
  16. return pos
  17. end
  18. end
  19. local function string_to_pos(pos)
  20. if type(pos) == 'string' then
  21. pos = minetest.string_to_pos(pos)
  22. end
  23. if type(pos) == 'table' then
  24. return vector.round(pos)
  25. end
  26. end
  27. -- Set the HUD position
  28. local hud_id
  29. function advmarkers.set_hud_pos(pos, title)
  30. pos = string_to_pos(pos)
  31. if not pos then return end
  32. -- Fall back to /mrkr if hud_add doesn't exist (Minetest 0.4).
  33. if not minetest.localplayer or not minetest.localplayer.hud_add or
  34. not minetest.localplayer.hud_change then
  35. minetest.run_server_chatcommand('mrkr', tostring(pos.x) .. ' ' ..
  36. tostring(pos.y) .. ' ' .. tostring(pos.z))
  37. end
  38. if not title then
  39. title = pos.x .. ', ' .. pos.y .. ', ' .. pos.z
  40. end
  41. if hud_id then
  42. minetest.localplayer:hud_change(hud_id, 'name', title)
  43. minetest.localplayer:hud_change(hud_id, 'world_pos', pos)
  44. else
  45. hud_id = minetest.localplayer:hud_add({
  46. hud_elem_type = 'waypoint',
  47. name = title,
  48. text = 'm',
  49. number = 0x00ffff,
  50. world_pos = pos
  51. })
  52. end
  53. minetest.display_chat_message('Waypoint set to ' ..
  54. minetest.colorize('#00ffff', title))
  55. return true
  56. end
  57. -- Add a waypoint
  58. function advmarkers.set_waypoint(pos, name)
  59. pos = pos_to_string(pos)
  60. if not pos then return end
  61. storage:set_string('marker-' .. tostring(name), pos)
  62. return true
  63. end
  64. advmarkers.set_marker = advmarkers.set_waypoint
  65. -- Delete a waypoint
  66. function advmarkers.delete_waypoint(name)
  67. storage:set_string('marker-' .. tostring(name), '')
  68. end
  69. advmarkers.delete_marker = advmarkers.delete_waypoint
  70. -- Get a waypoint
  71. function advmarkers.get_waypoint(name)
  72. return string_to_pos(storage:get_string('marker-' .. tostring(name)))
  73. end
  74. advmarkers.get_marker = advmarkers.get_waypoint
  75. -- Rename a waypoint and re-interpret the position.
  76. function advmarkers.rename_waypoint(oldname, newname)
  77. oldname, newname = tostring(oldname), tostring(newname)
  78. local pos = advmarkers.get_waypoint(oldname)
  79. if not pos or not advmarkers.set_waypoint(pos, newname) then return end
  80. if oldname ~= newname then
  81. advmarkers.delete_waypoint(oldname)
  82. end
  83. return true
  84. end
  85. advmarkers.rename_marker = advmarkers.rename_waypoint
  86. -- Display a waypoint
  87. function advmarkers.display_waypoint(name)
  88. return advmarkers.set_hud_pos(advmarkers.get_waypoint(name), name)
  89. end
  90. advmarkers.display_marker = advmarkers.display_waypoint
  91. -- Export waypoints
  92. function advmarkers.export(raw)
  93. local s = storage:to_table().fields
  94. if raw == 'M' then
  95. s = minetest.compress(minetest.serialize(s))
  96. s = 'M' .. minetest.encode_base64(s)
  97. elseif not raw then
  98. s = minetest.compress(minetest.write_json(s))
  99. s = 'J' .. minetest.encode_base64(s)
  100. end
  101. return s
  102. end
  103. -- Import waypoints
  104. function advmarkers.import(s)
  105. if type(s) ~= 'table' then
  106. local ver = s:sub(1, 1)
  107. if ver ~= 'M' and ver ~= 'J' then return end
  108. s = minetest.decode_base64(s:sub(2))
  109. local success, msg = pcall(minetest.decompress, s)
  110. if not success then return end
  111. if ver == 'M' then
  112. s = minetest.deserialize(msg, true)
  113. else
  114. s = minetest.parse_json(msg)
  115. end
  116. end
  117. -- Iterate over waypoints to preserve existing ones and check for errors.
  118. if type(s) == 'table' then
  119. for name, pos in pairs(s) do
  120. if type(name) == 'string' and type(pos) == 'string' and
  121. name:sub(1, 7) == 'marker-' and minetest.string_to_pos(pos) and
  122. storage:get_string(name) ~= pos then
  123. -- Prevent collisions
  124. local c = 0
  125. while #storage:get_string(name) > 0 and c < 50 do
  126. name = name .. '_'
  127. c = c + 1
  128. end
  129. -- Sanity check
  130. if c < 50 then
  131. storage:set_string(name, pos)
  132. end
  133. end
  134. end
  135. return true
  136. end
  137. end
  138. -- Get the waypoints formspec
  139. local formspec_list = {}
  140. local selected_name = false
  141. function advmarkers.display_formspec()
  142. local formspec = 'size[5.25,8]' ..
  143. 'label[0,0;Waypoint list]' ..
  144. 'button_exit[0,7.5;1.3125,0.5;display;Display]' ..
  145. 'button[1.3125,7.5;1.3125,0.5;teleport;Teleport]' ..
  146. 'button[2.625,7.5;1.3125,0.5;rename;Rename]' ..
  147. 'button[3.9375,7.5;1.3125,0.5;delete;Delete]' ..
  148. 'textlist[0,0.75;5,6;marker;'
  149. -- Iterate over all the waypoints
  150. local selected = 1
  151. formspec_list = {}
  152. local waypoints = {}
  153. for name, _ in pairs(storage:to_table().fields) do
  154. if name:sub(1, 7) == 'marker-' then
  155. table.insert(waypoints, name:sub(8))
  156. end
  157. end
  158. table.sort(waypoints)
  159. for id, name in ipairs(waypoints) do
  160. if id > 1 then
  161. formspec = formspec .. ','
  162. end
  163. if not selected_name then
  164. selected_name = name
  165. end
  166. if name == selected_name then
  167. selected = id
  168. end
  169. formspec_list[#formspec_list + 1] = name
  170. formspec = formspec .. '##' .. minetest.formspec_escape(name)
  171. end
  172. -- Close the text list and display the selected waypoint position
  173. formspec = formspec .. ';' .. tostring(selected) .. ']'
  174. if selected_name then
  175. local pos = advmarkers.get_waypoint(selected_name)
  176. if pos then
  177. pos = minetest.formspec_escape(tostring(pos.x) .. ', ' ..
  178. tostring(pos.y) .. ', ' .. tostring(pos.z))
  179. pos = 'Waypoint position: ' .. pos
  180. formspec = formspec .. 'label[0,6.75;' .. pos .. ']'
  181. end
  182. else
  183. -- Draw over the buttons
  184. formspec = formspec .. 'button_exit[0,7.5;5.25,0.5;quit;Close dialog]' ..
  185. 'label[0,6.75;No waypoints. Add one with ".add_mrkr".]'
  186. end
  187. -- Display the formspec
  188. return minetest.show_formspec('advmarkers-csm', formspec)
  189. end
  190. function advmarkers.get_chatcommand_pos(pos)
  191. if pos == 'h' or pos == 'here' then
  192. pos = minetest.localplayer:get_pos()
  193. elseif pos == 't' or pos == 'there' then
  194. if not advmarkers.last_coords then
  195. return false, 'No-one has used ".coords" and you have not died!'
  196. end
  197. pos = advmarkers.last_coords
  198. else
  199. pos = string_to_pos(pos)
  200. if not pos then
  201. return false, 'Invalid position!'
  202. end
  203. end
  204. return pos
  205. end
  206. local function register_chatcommand_alias(old, ...)
  207. local def = assert(minetest.registered_chatcommands[old])
  208. def.name = nil
  209. for i = 1, select('#', ...) do
  210. minetest.register_chatcommand(select(i, ...), table.copy(def))
  211. end
  212. end
  213. -- Open the waypoints GUI
  214. minetest.register_chatcommand('mrkr', {
  215. params = '',
  216. description = 'Open the advmarkers GUI',
  217. func = function(param)
  218. if param == '' then
  219. advmarkers.display_formspec()
  220. else
  221. local pos, err = advmarkers.get_chatcommand_pos(param)
  222. if not pos then
  223. return false, err
  224. end
  225. if not advmarkers.set_hud_pos(pos) then
  226. return false, 'Error setting the waypoint!'
  227. end
  228. end
  229. end
  230. })
  231. register_chatcommand_alias('mrkr', 'wp', 'wps', 'waypoint', 'waypoints')
  232. -- Add a waypoint
  233. minetest.register_chatcommand('add_mrkr', {
  234. params = '<pos / "here" / "there"> <name>',
  235. description = 'Adds a waypoint.',
  236. func = function(param)
  237. local s, e = param:find(' ')
  238. if not s or not e then
  239. return false, 'Invalid syntax! See .help add_mrkr for more info.'
  240. end
  241. local pos = param:sub(1, s - 1)
  242. local name = param:sub(e + 1)
  243. -- Validate the position
  244. local pos, err = advmarkers.get_chatcommand_pos(pos)
  245. if not pos then
  246. return false, err
  247. end
  248. -- Validate the name
  249. if not name or #name < 1 then
  250. return false, 'Invalid name!'
  251. end
  252. -- Set the waypoint
  253. return advmarkers.set_waypoint(pos, name), 'Done!'
  254. end
  255. })
  256. register_chatcommand_alias('add_mrkr', 'add_wp', 'add_waypoint')
  257. -- Set the HUD
  258. minetest.register_on_formspec_input(function(formname, fields)
  259. if formname == 'advmarkers-ignore' then
  260. return true
  261. elseif formname ~= 'advmarkers-csm' then
  262. return
  263. end
  264. local name = false
  265. if fields.marker then
  266. local event = minetest.explode_textlist_event(fields.marker)
  267. if event.index then
  268. name = formspec_list[event.index]
  269. end
  270. else
  271. name = selected_name
  272. end
  273. if name then
  274. if fields.display then
  275. if not advmarkers.display_waypoint(name) then
  276. minetest.display_chat_message('Error displaying waypoint!')
  277. end
  278. elseif fields.rename then
  279. minetest.show_formspec('advmarkers-csm', 'size[6,3]' ..
  280. 'label[0.35,0.2;Rename waypoint]' ..
  281. 'field[0.3,1.3;6,1;new_name;New name;' ..
  282. minetest.formspec_escape(name) .. ']' ..
  283. 'button[0,2;3,1;cancel;Cancel]' ..
  284. 'button[3,2;3,1;rename_confirm;Rename]')
  285. elseif fields.rename_confirm then
  286. if fields.new_name and #fields.new_name > 0 then
  287. if advmarkers.rename_waypoint(name, fields.new_name) then
  288. selected_name = fields.new_name
  289. else
  290. minetest.display_chat_message('Error renaming waypoint!')
  291. end
  292. advmarkers.display_formspec()
  293. else
  294. minetest.display_chat_message(
  295. 'Please enter a new name for the marker.'
  296. )
  297. end
  298. elseif fields.teleport then
  299. minetest.show_formspec('advmarkers-csm', 'size[6,2.2]' ..
  300. 'label[0.35,0.25;' .. minetest.formspec_escape(
  301. 'Teleport to a waypoint\n - ' .. name
  302. ) .. ']' ..
  303. 'button[0,1.25;2,1;cancel;Cancel]' ..
  304. 'button_exit[2,1.25;1,1;teleport_tpj;/tpj]' ..
  305. 'button_exit[3,1.25;1,1;teleport_tpc;/tpc]' ..
  306. 'button_exit[4,1.25;2,1;teleport_confirm;/teleport]')
  307. elseif fields.teleport_tpj then
  308. -- Teleport with /tpj
  309. local pos = advmarkers.get_waypoint(name)
  310. if pos and minetest.localplayer then
  311. local cpos = minetest.localplayer:get_pos()
  312. for _, dir in ipairs({'x', 'y', 'z'}) do
  313. local distance = pos[dir] - cpos[dir]
  314. minetest.run_server_chatcommand('tpj', dir .. ' ' ..
  315. tostring(distance))
  316. end
  317. else
  318. minetest.display_chat_message('Error teleporting to waypoint!')
  319. end
  320. elseif fields.teleport_confirm or fields.teleport_tpc then
  321. -- Teleport with /teleport
  322. local pos = advmarkers.get_waypoint(name)
  323. local cmd
  324. if fields.teleport_confirm then
  325. cmd = 'teleport'
  326. else
  327. cmd = 'tpc'
  328. end
  329. if pos and minetest.localplayer then
  330. minetest.run_server_chatcommand(cmd,
  331. pos.x .. ',' .. pos.y .. ',' .. pos.z)
  332. else
  333. minetest.display_chat_message('Error teleporting to waypoint!')
  334. end
  335. elseif fields.delete then
  336. minetest.show_formspec('advmarkers-csm', 'size[6,2]' ..
  337. 'label[0.35,0.25;Are you sure you want to delete this waypoint?]' ..
  338. 'button[0,1;3,1;cancel;Cancel]' ..
  339. 'button[3,1;3,1;delete_confirm;Delete]')
  340. elseif fields.delete_confirm then
  341. advmarkers.delete_waypoint(name)
  342. selected_name = false
  343. advmarkers.display_formspec()
  344. elseif fields.cancel then
  345. advmarkers.display_formspec()
  346. elseif name ~= selected_name then
  347. selected_name = name
  348. advmarkers.display_formspec()
  349. end
  350. elseif fields.display or fields.delete then
  351. minetest.display_chat_message('Please select a waypoint.')
  352. end
  353. return true
  354. end)
  355. -- Auto-add waypoints on death.
  356. minetest.register_on_death(function()
  357. if minetest.localplayer then
  358. local name = 'Death waypoint'
  359. local pos = minetest.localplayer:get_pos()
  360. advmarkers.last_coords = pos
  361. advmarkers.set_waypoint(pos, name)
  362. minetest.display_chat_message('Added waypoint "' .. name .. '".')
  363. end
  364. end)
  365. -- Allow string exporting
  366. minetest.register_chatcommand('mrkr_export', {
  367. params = '[old]',
  368. description = 'Exports an advmarkers string containing all your markers.',
  369. func = function(param)
  370. local export
  371. if param == 'old' then
  372. export = advmarkers.export('M')
  373. else
  374. export = advmarkers.export()
  375. end
  376. minetest.show_formspec('advmarkers-ignore',
  377. 'field[_;Your waypoint export string;' ..
  378. minetest.formspec_escape(export) .. ']')
  379. end
  380. })
  381. register_chatcommand_alias('mrkr_export', 'wp_export', 'waypoint_export')
  382. -- String importing
  383. minetest.register_chatcommand('mrkr_import', {
  384. params = '<advmarkers string>',
  385. description = 'Imports an advmarkers string. This will not overwrite ' ..
  386. 'existing markers that have the same name.',
  387. func = function(param)
  388. if advmarkers.import(param) then
  389. return true, 'Waypoints imported!'
  390. else
  391. return false, 'Invalid advmarkers string!'
  392. end
  393. end
  394. })
  395. register_chatcommand_alias('mrkr_export', 'wp_import', 'waypoint_import')
  396. -- Upload waypoints to the advmarkers server-side mod.
  397. minetest.register_chatcommand('mrkr_upload', {
  398. params = '',
  399. description = 'Uploads all waypoints to this server\'s advmarkers storage.',
  400. func = function(param)
  401. local data = advmarkers.export()
  402. minetest.run_server_chatcommand('mrkr_import', data)
  403. end
  404. })
  405. register_chatcommand_alias('mrkr_export', 'wp_upload', 'waypoint_upload')
  406. -- Chat channels .coords integration.
  407. -- You do not need to have chat channels installed for this to work.
  408. if not minetest.registered_on_receiving_chat_message then
  409. minetest.registered_on_receiving_chat_message =
  410. minetest.registered_on_receiving_chat_messages
  411. end
  412. table.insert(minetest.registered_on_receiving_chat_message, 1, function(msg)
  413. local s, e = msg:find('Current Position: %-?[0-9]+, %-?[0-9]+, %-?[0-9]+%.')
  414. if s and e then
  415. local pos = string_to_pos(msg:sub(s + 18, e - 1))
  416. if pos then
  417. advmarkers.last_coords = pos
  418. end
  419. end
  420. end)
  421. -- Add '.mrkrthere'
  422. minetest.register_chatcommand('mrkrthere', {
  423. params = '',
  424. description = 'Adds a (temporary) waypoint at the last ".coords" position.',
  425. func = function(param)
  426. if not advmarkers.last_coords then
  427. return false, 'No-one has used ".coords" and you have not died!'
  428. elseif not advmarkers.set_hud_pos(advmarkers.last_coords) then
  429. return false, 'Error setting the waypoint!'
  430. end
  431. end
  432. })
  433. minetest.register_chatcommand('clrmrkr', {
  434. params = '',
  435. description = 'Hides the displayed waypoint.',
  436. func = function(param)
  437. if hud_id then
  438. minetest.localplayer:hud_remove(hud_id)
  439. hud_id = nil
  440. return true, 'Hidden the currently displayed waypoint.'
  441. elseif not minetest.localplayer.hud_add then
  442. minetest.run_server_chatcommand('clrmrkr')
  443. return
  444. elseif not hud_id then
  445. return false, 'No waypoint is currently being displayed!'
  446. end
  447. end,
  448. })
  449. register_chatcommand_alias('clrmrkr', 'clear_marker', 'clrwp',
  450. 'clear_waypoint')