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.

init.lua 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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. -- TODO: Make this entirely client-side or allow the command to be changed.
  29. function advmarkers.set_hud_pos(pos)
  30. pos = string_to_pos(pos)
  31. if not pos then return end
  32. minetest.run_server_chatcommand('mrkr', tostring(pos.x) .. ' ' ..
  33. tostring(pos.y) .. ' ' .. tostring(pos.z))
  34. return true
  35. end
  36. -- Add a marker
  37. function advmarkers.set_marker(pos, name)
  38. pos = pos_to_string(pos)
  39. if not pos then return end
  40. storage:set_string('marker-' .. tostring(name), pos)
  41. return true
  42. end
  43. -- Delete a marker
  44. function advmarkers.delete_marker(name)
  45. storage:set_string('marker-' .. tostring(name), '')
  46. end
  47. -- Get a marker
  48. function advmarkers.get_marker(name)
  49. return string_to_pos(storage:get_string('marker-' .. tostring(name)))
  50. end
  51. -- Rename a marker and re-interpret the position.
  52. function advmarkers.rename_marker(oldname, newname)
  53. oldname, newname = tostring(oldname), tostring(newname)
  54. local pos = advmarkers.get_marker(oldname)
  55. if not pos or not advmarkers.set_marker(pos, newname) then return end
  56. if oldname ~= newname then
  57. advmarkers.delete_marker(oldname)
  58. end
  59. return true
  60. end
  61. -- Display a marker
  62. function advmarkers.display_marker(name)
  63. return advmarkers.set_hud_pos(advmarkers.get_marker(name))
  64. end
  65. -- Export markers
  66. function advmarkers.export(raw)
  67. local s = storage:to_table().fields
  68. if raw == 'M' then
  69. s = minetest.compress(minetest.serialize(s))
  70. s = 'M' .. minetest.encode_base64(s)
  71. elseif not raw then
  72. s = minetest.compress(minetest.write_json(s))
  73. s = 'J' .. minetest.encode_base64(s)
  74. end
  75. return s
  76. end
  77. -- Import markers
  78. function advmarkers.import(s)
  79. if type(s) ~= 'table' then
  80. local ver = s:sub(1, 1)
  81. if ver ~= 'M' and ver ~= 'J' then return end
  82. s = minetest.decode_base64(s:sub(2))
  83. local success, msg = pcall(minetest.decompress, s)
  84. if not success then return end
  85. if ver == 'M' then
  86. s = minetest.deserialize(msg)
  87. else
  88. s = minetest.parse_json(msg)
  89. end
  90. end
  91. -- Iterate over markers to preserve existing ones and check for errors.
  92. if type(s) == 'table' then
  93. for name, pos in pairs(s) do
  94. if type(name) == 'string' and type(pos) == 'string' and
  95. name:sub(1, 7) == 'marker-' and minetest.string_to_pos(pos) and
  96. storage:get_string(name) ~= pos then
  97. -- Prevent collisions
  98. local c = 0
  99. while #storage:get_string(name) > 0 and c < 50 do
  100. name = name .. '_'
  101. c = c + 1
  102. end
  103. -- Sanity check
  104. if c < 50 then
  105. storage:set_string(name, pos)
  106. end
  107. end
  108. end
  109. return true
  110. end
  111. end
  112. -- Get the markers formspec
  113. local formspec_list = {}
  114. local selected_name = false
  115. function advmarkers.display_formspec()
  116. local formspec = 'size[5.25,8]' ..
  117. 'label[0,0;Marker list]' ..
  118. 'button_exit[0,7.5;1.3125,0.5;display;Display]' ..
  119. 'button[1.3125,7.5;1.3125,0.5;teleport;Teleport]' ..
  120. 'button[2.625,7.5;1.3125,0.5;rename;Rename]' ..
  121. 'button[3.9375,7.5;1.3125,0.5;delete;Delete]' ..
  122. 'textlist[0,0.75;5,6;marker;'
  123. -- Iterate over all the markers
  124. local id = 0
  125. local selected = 1
  126. formspec_list = {}
  127. for name, pos in pairs(storage:to_table().fields) do
  128. if name:sub(1, 7) == 'marker-' then
  129. id = id + 1
  130. if id > 1 then
  131. formspec = formspec .. ','
  132. end
  133. name = name:sub(8)
  134. if not selected_name then
  135. selected_name = name
  136. end
  137. if name == selected_name then
  138. selected = id
  139. end
  140. formspec_list[#formspec_list + 1] = name
  141. formspec = formspec .. '##' .. minetest.formspec_escape(name)
  142. end
  143. end
  144. -- Close the text list and display the selected marker position
  145. formspec = formspec .. ';' .. tostring(selected) .. ']'
  146. if selected_name then
  147. local pos = advmarkers.get_marker(selected_name)
  148. if pos then
  149. pos = minetest.formspec_escape(tostring(pos.x) .. ', ' ..
  150. tostring(pos.y) .. ', ' .. tostring(pos.z))
  151. pos = 'Marker position: ' .. pos
  152. formspec = formspec .. 'label[0,6.75;' .. pos .. ']'
  153. end
  154. else
  155. -- Draw over the buttons
  156. formspec = formspec .. 'button_exit[0,7.5;5.25,0.5;quit;Close dialog]' ..
  157. 'label[0,6.75;No markers. Add one with ".add_mrkr".]'
  158. end
  159. -- Display the formspec
  160. return minetest.show_formspec('advmarkers-csm', formspec)
  161. end
  162. -- Open the markers GUI
  163. minetest.register_chatcommand('mrkr', {
  164. params = '',
  165. description = 'Open the advmarkers GUI',
  166. func = advmarkers.display_formspec
  167. })
  168. -- Add a marker
  169. minetest.register_chatcommand('add_mrkr', {
  170. params = '<pos / "here" / "there"> <name>',
  171. description = 'Adds a marker.',
  172. func = function(param)
  173. local s, e = param:find(' ')
  174. if not s or not e then
  175. return false, 'Invalid syntax! See .help add_mrkr for more info.'
  176. end
  177. local pos = param:sub(1, s - 1)
  178. local name = param:sub(e + 1)
  179. -- Validate the position
  180. if pos == 'here' then
  181. pos = minetest.localplayer:get_pos()
  182. elseif pos == 'there' then
  183. if not advmarkers.last_coords then
  184. return false, 'No-one has used ".coords" and you have not died!'
  185. end
  186. pos = advmarkers.last_coords
  187. else
  188. pos = string_to_pos(pos)
  189. if not pos then
  190. return false, 'Invalid position!'
  191. end
  192. end
  193. -- Validate the name
  194. if not name or #name < 1 then
  195. return false, 'Invalid name!'
  196. end
  197. -- Set the marker
  198. return advmarkers.set_marker(pos, name), 'Done!'
  199. end
  200. })
  201. -- Set the HUD
  202. minetest.register_on_formspec_input(function(formname, fields)
  203. if formname == 'advmarkers-ignore' then
  204. return true
  205. elseif formname ~= 'advmarkers-csm' then
  206. return
  207. end
  208. local name = false
  209. if fields.marker then
  210. local event = minetest.explode_textlist_event(fields.marker)
  211. if event.index then
  212. name = formspec_list[event.index]
  213. end
  214. else
  215. name = selected_name
  216. end
  217. if name then
  218. if fields.display then
  219. if not advmarkers.display_marker(name) then
  220. minetest.display_chat_message('Error displaying marker!')
  221. end
  222. elseif fields.rename then
  223. minetest.show_formspec('advmarkers-csm', 'size[6,3]' ..
  224. 'label[0.35,0.2;Rename marker]' ..
  225. 'field[0.3,1.3;6,1;new_name;New name;' ..
  226. minetest.formspec_escape(name) .. ']' ..
  227. 'button[0,2;3,1;cancel;Cancel]' ..
  228. 'button[3,2;3,1;rename_confirm;Rename]')
  229. elseif fields.rename_confirm then
  230. if fields.new_name and #fields.new_name > 0 then
  231. if advmarkers.rename_marker(name, fields.new_name) then
  232. selected_name = fields.new_name
  233. else
  234. minetest.display_chat_message('Error renaming marker!')
  235. end
  236. advmarkers.display_formspec()
  237. else
  238. minetest.display_chat_message(
  239. 'Please enter a new name for the marker.'
  240. )
  241. end
  242. elseif fields.teleport then
  243. minetest.show_formspec('advmarkers-csm', 'size[6,2.2]' ..
  244. 'label[0.35,0.25;' .. minetest.formspec_escape(
  245. 'Teleport to a marker\n - ' .. name
  246. ) .. ']' ..
  247. 'button[0,1.25;2,1;cancel;Cancel]' ..
  248. 'button_exit[2,1.25;1,1;teleport_tpj;/tpj]' ..
  249. 'button_exit[3,1.25;1,1;teleport_tpc;/tpc]' ..
  250. 'button_exit[4,1.25;2,1;teleport_confirm;/teleport]')
  251. elseif fields.teleport_tpj then
  252. -- Teleport with /tpj
  253. local pos = advmarkers.get_marker(name)
  254. if pos and minetest.localplayer then
  255. local cpos = minetest.localplayer:get_pos()
  256. for _, dir in ipairs({'x', 'y', 'z'}) do
  257. local distance = pos[dir] - cpos[dir]
  258. minetest.run_server_chatcommand('tpj', dir .. ' ' ..
  259. tostring(distance))
  260. end
  261. else
  262. minetest.display_chat_message('Error teleporting to marker!')
  263. end
  264. elseif fields.teleport_confirm or fields.teleport_tpc then
  265. -- Teleport with /teleport
  266. local pos = advmarkers.get_marker(name)
  267. local cmd
  268. if fields.teleport_confirm then
  269. cmd = 'teleport'
  270. else
  271. cmd = 'tpc'
  272. end
  273. if pos and minetest.localplayer then
  274. minetest.run_server_chatcommand(cmd,
  275. pos.x .. ',' .. pos.y .. ',' .. pos.z)
  276. else
  277. minetest.display_chat_message('Error teleporting to marker!')
  278. end
  279. elseif fields.delete then
  280. minetest.show_formspec('advmarkers-csm', 'size[6,2]' ..
  281. 'label[0.35,0.25;Are you sure you want to delete this marker?]' ..
  282. 'button[0,1;3,1;cancel;Cancel]' ..
  283. 'button[3,1;3,1;delete_confirm;Delete]')
  284. elseif fields.delete_confirm then
  285. advmarkers.delete_marker(name)
  286. selected_name = false
  287. advmarkers.display_formspec()
  288. elseif fields.cancel then
  289. advmarkers.display_formspec()
  290. elseif name ~= selected_name then
  291. selected_name = name
  292. advmarkers.display_formspec()
  293. end
  294. elseif fields.display or fields.delete then
  295. minetest.display_chat_message('Please select a marker.')
  296. end
  297. return true
  298. end)
  299. -- Auto-add markers on death.
  300. minetest.register_on_death(function()
  301. if minetest.localplayer then
  302. local name = os.date('Death on %Y-%m-%d %H:%M:%S')
  303. local pos = minetest.localplayer:get_pos()
  304. advmarkers.last_coords = pos
  305. advmarkers.set_marker(pos, name)
  306. minetest.display_chat_message('Added marker "' .. name .. '".')
  307. end
  308. end)
  309. -- Allow string exporting
  310. minetest.register_chatcommand('mrkr_export', {
  311. params = '[old]',
  312. description = 'Exports an advmarkers string containing all your markers.',
  313. func = function(param)
  314. local export
  315. if param == 'old' then
  316. export = advmarkers.export('M')
  317. else
  318. export = advmarkers.export()
  319. end
  320. minetest.show_formspec('advmarkers-ignore',
  321. 'field[_;Your marker export string;' ..
  322. minetest.formspec_escape(export) .. ']')
  323. end
  324. })
  325. -- String importing
  326. minetest.register_chatcommand('mrkr_import', {
  327. params = '<advmarkers string>',
  328. description = 'Imports an advmarkers string. This will not overwrite ' ..
  329. 'existing markers that have the same name.',
  330. func = function(param)
  331. if advmarkers.import(param) then
  332. return true, 'Markers imported!'
  333. else
  334. return false, 'Invalid advmarkers string!'
  335. end
  336. end
  337. })
  338. -- Upload markers to the advmarkers server-side mod.
  339. minetest.register_chatcommand('mrkr_upload', {
  340. params = '',
  341. description = 'Uploads all markers to this server\'s advmarkers storage.',
  342. func = function(param)
  343. local data = advmarkers.export()
  344. minetest.run_server_chatcommand('mrkr_import', data)
  345. end
  346. })
  347. -- Chat channels .coords integration.
  348. -- You do not need to have chat channels installed for this to work.
  349. if not minetest.registered_on_receiving_chat_message then
  350. minetest.registered_on_receiving_chat_message =
  351. minetest.registered_on_receiving_chat_messages
  352. end
  353. table.insert(minetest.registered_on_receiving_chat_message, 1, function(msg)
  354. local s, e = msg:find('Current Position: %-?[0-9]+, %-?[0-9]+, %-?[0-9]+%.')
  355. if s and e then
  356. local pos = string_to_pos(msg:sub(s + 18, e - 1))
  357. if pos then
  358. advmarkers.last_coords = pos
  359. end
  360. end
  361. end)
  362. -- Add '.mrkrthere'
  363. minetest.register_chatcommand('mrkrthere', {
  364. params = '',
  365. description = 'Adds a (temporary) marker at the last ".coords" position.',
  366. func = function(param)
  367. if not advmarkers.last_coords then
  368. return false, 'No-one has used ".coords" and you have not died!'
  369. elseif not advmarkers.set_hud_pos(advmarkers.last_coords) then
  370. return false, 'Error setting the marker!'
  371. end
  372. end
  373. })