init.lua 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. tpad = {}
  2. tpad.version = "1.2"
  3. tpad.mod_name = minetest.get_current_modname()
  4. tpad.texture = "tpad-texture.png"
  5. tpad.mesh = "tpad-mesh.obj"
  6. tpad.nodename = "tpad:tpad"
  7. tpad.mod_path = minetest.get_modpath(tpad.mod_name)
  8. tpad.settings_file = minetest.get_worldpath() .. "/mod_storage/" .. tpad.mod_name .. ".custom.conf"
  9. local PRIVATE_PAD_STRING = "Private (only owner)"
  10. local PUBLIC_PAD_STRING = "Public (only owner's network)"
  11. local GLOBAL_PAD_STRING = "Global (any network)"
  12. local PRIVATE_PAD = 1
  13. local PUBLIC_PAD = 2
  14. local GLOBAL_PAD = 4
  15. local RED_ESCAPE = minetest.get_color_escape_sequence("#FF0000")
  16. local GREEN_ESCAPE = minetest.get_color_escape_sequence("#00FF00")
  17. local BLUE_ESCAPE = minetest.get_color_escape_sequence("#0000FF")
  18. local YELLOW_ESCAPE = minetest.get_color_escape_sequence("#FFFF00")
  19. local CYAN_ESCAPE = minetest.get_color_escape_sequence("#00FFFF")
  20. local MAGENTA_ESCAPE = minetest.get_color_escape_sequence("#FF00FF")
  21. local WHITE_ESCAPE = minetest.get_color_escape_sequence("#FFFFFF")
  22. local OWNER_ESCAPE_COLOR = CYAN_ESCAPE
  23. local padtype_flag_to_string = {
  24. [PRIVATE_PAD] = PRIVATE_PAD_STRING,
  25. [PUBLIC_PAD] = PUBLIC_PAD_STRING,
  26. [GLOBAL_PAD] = GLOBAL_PAD_STRING,
  27. }
  28. local padtype_string_to_flag = {
  29. [PRIVATE_PAD_STRING] = PRIVATE_PAD,
  30. [PUBLIC_PAD_STRING] = PUBLIC_PAD,
  31. [GLOBAL_PAD_STRING] = GLOBAL_PAD,
  32. }
  33. local short_padtype_string = {
  34. [PRIVATE_PAD] = "private",
  35. [PUBLIC_PAD] = "public",
  36. [GLOBAL_PAD] = "global",
  37. }
  38. local smartfs = dofile(tpad.mod_path .. "/lib/smartfs.lua")
  39. local notify = dofile(tpad.mod_path .. "/notify.lua")
  40. -- workaround storage to tell the main dialog about the last clicked pad
  41. local last_clicked_pos = {}
  42. -- workaround storage to tell the main dialog about last selected pad in the lists
  43. local last_selected_index = {}
  44. local last_selected_global_index = {}
  45. -- memory of shown waypoints
  46. local waypoint_hud_ids = {}
  47. minetest.register_privilege("tpad_admin", {
  48. description = "Can edit and destroy any tpad",
  49. give_to_singleplayer = true,
  50. })
  51. -- ========================================================================
  52. -- local helpers
  53. -- ========================================================================
  54. local function copy_file(source, dest)
  55. local src_file = io.open(source, "rb")
  56. if not src_file then
  57. return false, "copy_file() unable to open source for reading"
  58. end
  59. local src_data = src_file:read("*all")
  60. src_file:close()
  61. local dest_file = io.open(dest, "wb")
  62. if not dest_file then
  63. return false, "copy_file() unable to open dest for writing"
  64. end
  65. dest_file:write(src_data)
  66. dest_file:close()
  67. return true, "files copied successfully"
  68. end
  69. -- alias to make copy_file() available to storage.lua
  70. tpad._copy_file = copy_file
  71. local function custom_or_default(modname, path, filename)
  72. local default_filename = "default/" .. filename
  73. local full_filename = path .. "/custom." .. filename
  74. local full_default_filename = path .. "/" .. default_filename
  75. os.rename(path .. "/" .. filename, full_filename)
  76. local file = io.open(full_filename, "rb")
  77. if not file then
  78. minetest.debug("[" .. modname .. "] Copying " .. default_filename .. " to " .. filename .. " (path: " .. path .. ")")
  79. local success, err = copy_file(full_default_filename, full_filename)
  80. if not success then
  81. minetest.debug("[" .. modname .. "] " .. err)
  82. return false
  83. end
  84. file = io.open(full_filename, "rb")
  85. if not file then
  86. minetest.debug("[" .. modname .. "] Unable to load " .. filename .. " file from path " .. path)
  87. return false
  88. end
  89. end
  90. file:close()
  91. return full_filename
  92. end
  93. -- load storage facilities and verify it
  94. dofile(tpad.mod_path .. "/storage.lua")
  95. -- ========================================================================
  96. -- load custom recipe
  97. -- ========================================================================
  98. local recipes_filename = custom_or_default(tpad.mod_name, tpad.mod_path, "recipes.lua")
  99. if recipes_filename then
  100. local recipes = dofile(recipes_filename)
  101. if type(recipes) == "table" and recipes[tpad.nodename] then
  102. minetest.register_craft({
  103. output = tpad.nodename,
  104. recipe = recipes[tpad.nodename],
  105. })
  106. end
  107. end
  108. -- ========================================================================
  109. -- callback bound in register_chatcommand("tpad")
  110. -- ========================================================================
  111. function tpad.command(playername, param)
  112. tpad.hud_off(playername)
  113. if(param == "off") then return end
  114. local player = minetest.get_player_by_name(playername)
  115. local pads = tpad._get_stored_pads(playername)
  116. local shortest_distance = nil
  117. local closest_pad = nil
  118. local playerpos = player:getpos()
  119. for strpos, pad in pairs(pads) do
  120. local pos = minetest.string_to_pos(strpos)
  121. local distance = vector.distance(pos, playerpos)
  122. if not shortest_distance or distance < shortest_distance then
  123. closest_pad = {
  124. pos = pos,
  125. name = pad.name .. " " .. strpos,
  126. }
  127. shortest_distance = distance
  128. end
  129. end
  130. if closest_pad then
  131. waypoint_hud_ids[playername] = player:hud_add({
  132. hud_elem_type = "waypoint",
  133. name = closest_pad.name,
  134. world_pos = closest_pad.pos,
  135. number = 0xFF0000,
  136. })
  137. notify(playername, "Waypoint to " .. closest_pad.name .. " displayed")
  138. end
  139. end
  140. function tpad.hud_off(playername)
  141. local player = minetest.get_player_by_name(playername)
  142. local hud_id = waypoint_hud_ids[playername]
  143. if hud_id then
  144. player:hud_remove(hud_id)
  145. end
  146. end
  147. -- ========================================================================
  148. -- callbacks bound in register_node()
  149. -- ========================================================================
  150. function tpad.get_pos_from_pointed(pointed)
  151. local node_above = minetest.get_node_or_nil(pointed.above)
  152. local node_under = minetest.get_node_or_nil(pointed.under)
  153. if not node_above or not node_under then return end
  154. local def_above = minetest.registered_nodes[node_above.name]
  155. or minetest.nodedef_default
  156. local def_under = minetest.registered_nodes[node_under.name]
  157. or minetest.nodedef_default
  158. if not def_above.buildable_to and not def_under.buildable_to then return end
  159. if def_under.buildable_to then
  160. return pointed.under
  161. end
  162. return pointed.above
  163. end
  164. function tpad.on_place(itemstack, placer, pointed_thing)
  165. if tpad.max_total_pads_reached(placer) then
  166. notify.warn(placer, "You can't place any more pads")
  167. return itemstack
  168. end
  169. local pos = tpad.get_pos_from_pointed(pointed_thing) or {}
  170. itemstack = minetest.rotate_node(itemstack, placer, pointed_thing)
  171. local placed = minetest.get_node_or_nil(pos)
  172. if placed and placed.name == tpad.nodename then
  173. local meta = minetest.env:get_meta(pos)
  174. local playername = placer:get_player_name()
  175. meta:set_string("owner", playername)
  176. meta:set_string("infotext", "TPAD Station by " .. playername .. " - right click to interact")
  177. tpad.set_pad_data(pos, "", PRIVATE_PAD_STRING)
  178. end
  179. return itemstack
  180. end
  181. local submit = {}
  182. function tpad.set_max_total_pads(max)
  183. if not max then max = 0 end
  184. local settings = Settings(tpad.settings_file)
  185. settings:set("max_total_pads_per_player", max)
  186. settings:write()
  187. end
  188. function tpad.get_max_total_pads()
  189. local settings = Settings(tpad.settings_file)
  190. local max = tonumber(settings:get("max_total_pads_per_player"))
  191. if not max then
  192. tpad.set_max_total_pads(100)
  193. return 100
  194. end
  195. return max
  196. end
  197. tpad.get_max_total_pads()
  198. function tpad.set_max_global_pads(max)
  199. if not max then max = 0 end
  200. local settings = Settings(tpad.settings_file)
  201. settings:set("max_global_pads_per_player", max)
  202. settings:write()
  203. end
  204. function tpad.get_max_global_pads()
  205. local settings = Settings(tpad.settings_file)
  206. local max = tonumber(settings:get("max_global_pads_per_player"))
  207. if not max then
  208. tpad.set_max_global_pads(4)
  209. return 4
  210. end
  211. return max
  212. end
  213. tpad.get_max_global_pads()
  214. function tpad.max_total_pads_reached(placer)
  215. local placername = placer:get_player_name()
  216. if minetest.get_player_privs(placername).tpad_admin then
  217. return false
  218. end
  219. local localnet = submit.local_helper(placername)
  220. return #localnet.by_index >= tpad.get_max_total_pads()
  221. end
  222. function tpad.max_global_pads_reached(playername)
  223. if minetest.get_player_privs(playername).tpad_admin then
  224. return false
  225. end
  226. local localnet = submit.local_helper(playername)
  227. local count = 0
  228. for _, pad in pairs(localnet.by_name) do
  229. if pad.type == GLOBAL_PAD then
  230. count = count + 1
  231. end
  232. end
  233. return count >= tpad.get_max_global_pads()
  234. end
  235. function submit.global_helper()
  236. local allpads = tpad._get_all_pads()
  237. local result = {
  238. by_name = {},
  239. by_index = {},
  240. }
  241. for ownername, pads in pairs(allpads) do
  242. for strpos, pad in pairs(pads) do
  243. if pad.type == GLOBAL_PAD then
  244. pad = tpad.decorate_pad_data(strpos, pad, ownername)
  245. table.insert(result.by_index, pad.global_fullname)
  246. result.by_name[pad.global_fullname] = pad
  247. end
  248. end
  249. end
  250. table.sort(result.by_index)
  251. return result
  252. end
  253. function submit.local_helper(ownername, omit_private_pads)
  254. local pads = tpad._get_stored_pads(ownername)
  255. local result = {
  256. by_name = {},
  257. by_index = {},
  258. }
  259. for strpos, pad in pairs(pads) do
  260. local skip = omit_private_pads and pad.type == PRIVATE_PAD
  261. if not skip then
  262. pad = tpad.decorate_pad_data(strpos, pad, ownername)
  263. table.insert(result.by_index, pad.local_fullname)
  264. result.by_name[pad.local_fullname] = pad
  265. end
  266. end
  267. table.sort(result.by_index)
  268. return result
  269. end
  270. function submit.save(form)
  271. if form.playername ~= form.ownername and not minetest.get_player_privs(form.playername).tpad_admin then
  272. notify.warn(form.playername, "The selected pad doesn't belong to you")
  273. return
  274. end
  275. local padname = form.state:get("padname_field"):getText()
  276. local strpadtype = form.state:get("padtype_dropdown"):getSelectedItem()
  277. if strpadtype == GLOBAL_PAD_STRING and tpad.max_global_pads_reached(form.playername) then
  278. notify.warn(form.playername, "Can't add more pads to the Global Network, set to 'Public' instead")
  279. strpadtype = PUBLIC_PAD_STRING
  280. end
  281. tpad.set_pad_data(form.clicked_pos, padname, strpadtype)
  282. end
  283. function submit.teleport(form)
  284. local pads_listbox = form.state:get("pads_listbox")
  285. local selected_item = pads_listbox:getSelectedItem()
  286. local pad
  287. if form.globalnet then
  288. pad = form.globalnet.by_name[selected_item]
  289. else
  290. pad = form.localnet.by_name[selected_item]
  291. end
  292. if not pad then
  293. notify.err(form.playername, "Error! Missing pad data!")
  294. return
  295. end
  296. local player = minetest.get_player_by_name(form.playername)
  297. player:moveto(pad.pos, false)
  298. local padname = form.globalnet and pad.global_fullname or pad.local_fullname
  299. notify(form.playername, "Teleported to " .. padname)
  300. tpad.hud_off(form.playername)
  301. minetest.after(0, function()
  302. minetest.close_formspec(form.playername, form.formname)
  303. end)
  304. end
  305. function submit.admin(form)
  306. form.state = tpad.forms.admin:show(form.playername)
  307. form.formname = "tpad.forms.admin"
  308. local max_total_field = form.state:get("max_total_field")
  309. max_total_field:setText(tpad.get_max_total_pads())
  310. local max_global_field = form.state:get("max_global_field")
  311. max_global_field:setText(tpad.get_max_global_pads())
  312. local function admin_save()
  313. local max_total = tonumber(max_total_field:getText())
  314. local max_global = tonumber(max_global_field:getText())
  315. tpad.set_max_total_pads(max_total)
  316. tpad.set_max_global_pads(max_global)
  317. minetest.after(0, function()
  318. minetest.close_formspec(form.playername, form.formname)
  319. end)
  320. end
  321. max_total_field:onKeyEnter(admin_save)
  322. max_total_field:onKeyEnter(admin_save)
  323. form.state:get("save_button"):onClick(admin_save)
  324. end
  325. function submit.global(form)
  326. if minetest.get_player_privs(form.playername).tpad_admin then
  327. form.state = tpad.forms.global_network_admin:show(form.playername)
  328. form.formname = "tpad.forms.global_network_admin"
  329. form.state:get("admin_button"):onClick(function()
  330. minetest.after(0, function()
  331. submit.admin(form)
  332. end)
  333. end)
  334. else
  335. form.state = tpad.forms.global_network:show(form.playername)
  336. form.formname = "tpad.forms.global_network"
  337. end
  338. form.globalnet = submit.global_helper()
  339. local last_index = last_selected_global_index[form.playername]
  340. local pads_listbox = form.state:get("pads_listbox")
  341. pads_listbox:clearItems()
  342. for _, pad_item in ipairs(form.globalnet.by_index) do
  343. pads_listbox:addItem(pad_item)
  344. end
  345. pads_listbox:setSelected(last_index)
  346. pads_listbox:onClick(function()
  347. last_selected_global_index[form.playername] = pads_listbox:getSelected()
  348. end)
  349. pads_listbox:onDoubleClick(function() submit.teleport(form) end)
  350. form.state:get("teleport_button"):onClick(function() submit.teleport(form) end)
  351. form.state:get("local_button"):onClick(function()
  352. minetest.after(0, function()
  353. tpad.on_rightclick(form.clicked_pos, form.node, form.clicker)
  354. end)
  355. end)
  356. end
  357. function submit.delete(form)
  358. minetest.after(0, function()
  359. local pads_listbox = form.state:get("pads_listbox")
  360. local selected_item = pads_listbox:getSelectedItem()
  361. local delete_pad = form.localnet.by_name[selected_item]
  362. if not delete_pad then
  363. notify.warn(form.playername, "Please select a pad first")
  364. return
  365. end
  366. if form.playername ~= form.ownername and not minetest.get_player_privs(form.playername).tpad_admin then
  367. notify.warn(form.playername, "The selected pad doesn't belong to you")
  368. return
  369. end
  370. if minetest.pos_to_string(delete_pad.pos) == minetest.pos_to_string(form.clicked_pos) then
  371. notify.warn(form.playername, "You can't delete the current pad, destroy it manually")
  372. return
  373. end
  374. local function reshow_main()
  375. minetest.after(0, function()
  376. tpad.on_rightclick(form.clicked_pos, form.node, minetest.get_player_by_name(form.playername))
  377. end)
  378. end
  379. local delete_state = tpad.forms.confirm_pad_deletion:show(form.playername)
  380. delete_state:get("padname_label"):setText(
  381. YELLOW_ESCAPE .. delete_pad.local_fullname ..
  382. WHITE_ESCAPE .. " by " ..
  383. OWNER_ESCAPE_COLOR .. form.ownername
  384. )
  385. local confirm_button = delete_state:get("confirm_button")
  386. confirm_button:onClick(function()
  387. last_selected_index[form.playername .. ":" .. form.ownername] = nil
  388. tpad.del_pad(form.ownername, delete_pad.pos)
  389. minetest.remove_node(delete_pad.pos)
  390. notify(form.playername, "Pad " .. delete_pad.local_fullname .. " destroyed")
  391. reshow_main()
  392. end)
  393. local deny_button = delete_state:get("deny_button")
  394. deny_button:onClick(reshow_main)
  395. end)
  396. end
  397. function tpad.on_rightclick(clicked_pos, node, clicker)
  398. local playername = clicker:get_player_name()
  399. local clicked_meta = minetest.env:get_meta(clicked_pos)
  400. local ownername = clicked_meta:get_string("owner")
  401. local pad = tpad.get_pad_data(clicked_pos)
  402. if not pad or not ownername then
  403. notify.err(playername, "Error! Missing pad data!")
  404. return
  405. end
  406. local form = {}
  407. form.playername = playername
  408. form.clicker = clicker
  409. form.ownername = ownername
  410. form.clicked_pos = clicked_pos
  411. form.node = node
  412. form.omit_private_pads = false
  413. last_clicked_pos[playername] = clicked_pos;
  414. if ownername == playername or minetest.get_player_privs(playername).tpad_admin then
  415. form.formname = "tpad.forms.main_owner"
  416. form.state = tpad.forms.main_owner:show(playername)
  417. local padname_field = form.state:get("padname_field")
  418. padname_field:setLabel("This pad name (owned by " .. OWNER_ESCAPE_COLOR .. ownername .. WHITE_ESCAPE .. ")")
  419. padname_field:setText(pad.name)
  420. padname_field:onKeyEnter(function() submit.save(form) end)
  421. form.state:get("save_button"):onClick(function() submit.save(form) end)
  422. form.state:get("delete_button"):onClick(function() submit.delete(form) end)
  423. form.state:get("padtype_dropdown"):setSelectedItem(padtype_flag_to_string[pad.type])
  424. elseif pad.type == PRIVATE_PAD then
  425. notify.warn(playername, "This pad is private")
  426. return
  427. else
  428. form.omit_private_pads = true
  429. form.formname = "tpad.forms.main_visitor"
  430. form.state = tpad.forms.main_visitor:show(playername)
  431. form.state:get("visitor_label"):setText("Pad \"" .. pad.name .. "\", owned by " .. OWNER_ESCAPE_COLOR .. ownername)
  432. end
  433. form.localnet = submit.local_helper(ownername, form.omit_private_pads)
  434. local last_click_key = playername .. ":" .. ownername
  435. local last_index = last_selected_index[last_click_key]
  436. local pads_listbox = form.state:get("pads_listbox")
  437. pads_listbox:clearItems()
  438. for _, pad_item in ipairs(form.localnet.by_index) do
  439. pads_listbox:addItem(pad_item)
  440. end
  441. pads_listbox:setSelected(last_index)
  442. pads_listbox:onClick(function()
  443. last_selected_index[last_click_key] = pads_listbox:getSelected()
  444. end)
  445. pads_listbox:onDoubleClick(function() submit.teleport(form) end)
  446. form.state:get("teleport_button"):onClick(function() submit.teleport(form) end)
  447. form.state:get("global_button"):onClick(function()
  448. minetest.after(0, function()
  449. submit.global(form)
  450. end)
  451. end)
  452. end
  453. function tpad.can_dig(pos, player)
  454. local meta = minetest.env:get_meta(pos)
  455. local ownername = meta:get_string("owner")
  456. local playername = player:get_player_name()
  457. if ownername == "" or ownername == nil or playername == ownername
  458. or minetest.get_player_privs(playername).tpad_admin then
  459. return true
  460. end
  461. notify.warn(playername, "This pad doesn't belong to you")
  462. return false
  463. end
  464. function tpad.on_destruct(pos)
  465. local meta = minetest.env:get_meta(pos)
  466. local ownername = meta:get_string("owner")
  467. tpad.del_pad(ownername, pos)
  468. end
  469. -- ========================================================================
  470. -- forms
  471. -- ========================================================================
  472. tpad.forms = {}
  473. local function forms_add_padlist(state, is_global)
  474. local pads_listbox = state:listbox(0.2, 2.4, 7.6, 4, "pads_listbox", {})
  475. local teleport_button = state:button(0.2, 7, 1.5, 0, "teleport_button", "Teleport")
  476. local close_button = state:button(6.5, 7, 1.5, 0, "close_button", "Close")
  477. close_button:setClose(true)
  478. if is_global then
  479. local local_button = state:button(1.8, 7, 2.5, 0, "local_button", "Local Network")
  480. local_button:setClose(true)
  481. else
  482. local global_button = state:button(1.8, 7, 2.5, 0, "global_button", "Global Network")
  483. global_button:setClose(true)
  484. end
  485. state:label(0.2, 7.5, "teleport_label", "(you can doubleclick on a pad to teleport)")
  486. end
  487. tpad.forms.main_owner = smartfs.create("tpad.forms.main_owner", function(state)
  488. state:size(8, 8);
  489. state:field(0.5, 1, 6, 0, "padname_field", "", "")
  490. local save_button = state:button(6.5, 0.7, 1.5, 0, "save_button", "Save")
  491. save_button:setClose(true)
  492. local padtype_dropdown = state:dropdown(0.2, 1.2, 6.4, 0, "padtype_dropdown")
  493. padtype_dropdown:addItem(PRIVATE_PAD_STRING)
  494. padtype_dropdown:addItem(PUBLIC_PAD_STRING)
  495. padtype_dropdown:addItem(GLOBAL_PAD_STRING)
  496. local delete_button = state:button(4.4, 7, 1.5, 0, "delete_button", "Delete")
  497. forms_add_padlist(state)
  498. end)
  499. tpad.forms.main_visitor = smartfs.create("tpad.forms.main_visitor", function(state)
  500. state:size(8, 8)
  501. state:label(0.2, 1, "visitor_label", "")
  502. forms_add_padlist(state)
  503. end)
  504. tpad.forms.confirm_pad_deletion = smartfs.create("tpad.forms.confirm_pad_deletion", function(state)
  505. state:size(8, 2.5)
  506. state:label(0, 0, "intro_label", "Are you sure you want to destroy pad")
  507. state:label(0, 0.5, "padname_label", "")
  508. state:label(0, 1, "outro_label", "(you will not get the pad back)")
  509. state:button(0, 2.2, 2, 0, "confirm_button", "Yes, delete it")
  510. state:button(6, 2.2, 2, 0, "deny_button", "No, keep it")
  511. end)
  512. tpad.forms.global_network = smartfs.create("tpad.forms.global_network", function(state)
  513. state:size(8, 8)
  514. state:label(0.2, 1, "visitor_label", "Pick a pad from the Global Pads Network")
  515. local is_global = true
  516. forms_add_padlist(state, is_global)
  517. end)
  518. tpad.forms.global_network_admin = smartfs.create("tpad.forms.global_network_admin", function(state)
  519. state:size(8, 8)
  520. state:label(0.2, 1, "visitor_label", "Pick a pad from the Global Pads Network")
  521. local admin_button = state:button(4.4, 7, 1.5, 0, "admin_button", "Admin")
  522. admin_button:setClose(true)
  523. local is_global = true
  524. forms_add_padlist(state, is_global)
  525. end)
  526. tpad.forms.admin = smartfs.create("tpad.forms.admin", function(state)
  527. state:size(8, 8)
  528. state:label(0.2, 0.2, "admin_label", "TPAD Settings")
  529. state:field(0.5, 2, 6, 0, "max_total_field", "Max total pads per player")
  530. state:field(0.5, 3.5, 6, 0, "max_global_field", "Max global pads per player")
  531. local save_button = state:button(6.5, 0.7, 1.5, 0, "save_button", "Save")
  532. save_button:setClose(true)
  533. local close_button = state:button(6.5, 7, 1.5, 0, "close_button", "Close")
  534. close_button:setClose(true)
  535. end)
  536. -- ========================================================================
  537. -- helper functions
  538. -- ========================================================================
  539. function tpad.decorate_pad_data(pos, pad, ownername)
  540. pad = table.copy(pad)
  541. if type(pos) == "string" then
  542. pad.strpos = pos
  543. pad.pos = minetest.string_to_pos(pos)
  544. else
  545. pad.pos = pos
  546. pad.strpos = minetest.pos_to_string(pos)
  547. end
  548. pad.owner = ownername
  549. pad.name = pad.name or ""
  550. pad.type = pad.type or PUBLIC_PAD
  551. pad.local_fullname = pad.name .. " " .. pad.strpos .. " " .. short_padtype_string[pad.type]
  552. pad.global_fullname = "[" .. ownername .. "] " .. pad.name .. " " .. pad.strpos
  553. return pad
  554. end
  555. function tpad.get_pad_data(pos)
  556. local meta = minetest.env:get_meta(pos)
  557. local ownername = meta:get_string("owner")
  558. local pads = tpad._get_stored_pads(ownername)
  559. local strpos = minetest.pos_to_string(pos)
  560. local pad = pads[strpos]
  561. if not pad then return end
  562. return tpad.decorate_pad_data(pos, pad, ownername)
  563. end
  564. function tpad.set_pad_data(pos, padname, padtype)
  565. local meta = minetest.env:get_meta(pos)
  566. local ownername = meta:get_string("owner")
  567. local pads = tpad._get_stored_pads(ownername)
  568. local strpos = minetest.pos_to_string(pos)
  569. local pad = pads[strpos]
  570. if not pad then
  571. pad = {}
  572. end
  573. pad.name = padname
  574. pad.type = padtype_string_to_flag[padtype]
  575. pads[strpos] = pad
  576. tpad._set_stored_pads(ownername, pads)
  577. end
  578. function tpad.del_pad(ownername, pos)
  579. local pads = tpad._get_stored_pads(ownername)
  580. pads[minetest.pos_to_string(pos)] = nil
  581. tpad._set_stored_pads(ownername, pads)
  582. end
  583. -- ========================================================================
  584. -- register node and bind callbacks
  585. -- ========================================================================
  586. local collision_box = {
  587. type = "fixed",
  588. fixed = {
  589. { -0.5, -0.5, -0.5, 0.5, -0.3, 0.5 },
  590. }
  591. }
  592. minetest.register_node(tpad.nodename, {
  593. drawtype = "mesh",
  594. tiles = { tpad.texture },
  595. mesh = tpad.mesh,
  596. paramtype = "light",
  597. paramtype2 = "facedir",
  598. on_place = tpad.on_place,
  599. collision_box = collision_box,
  600. selection_box = collision_box,
  601. description = "Teleporter Pad",
  602. groups = {choppy = 2, dig_immediate = 2},
  603. on_rightclick = tpad.on_rightclick,
  604. can_dig = tpad.can_dig,
  605. on_destruct = tpad.on_destruct,
  606. on_blast = function(pos)
  607. local meta = minetest.env:get_meta(pos)
  608. local ownername = meta:get_string("owner")
  609. tpad.del_pad(ownername, pos)
  610. minetest.remove_node(pos)
  611. end,
  612. })
  613. minetest.register_chatcommand("tpad", {func = tpad.command})