init.lua 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066
  1. local S = minetest.get_translator("testtools")
  2. local F = minetest.formspec_escape
  3. testtools = {}
  4. dofile(minetest.get_modpath("testtools") .. "/light.lua")
  5. dofile(minetest.get_modpath("testtools") .. "/privatizer.lua")
  6. dofile(minetest.get_modpath("testtools") .. "/particles.lua")
  7. local pointabilities_nodes = {
  8. nodes = {
  9. ["group:blocking_pointable_test"] = true,
  10. ["group:not_pointable_test"] = true,
  11. },
  12. }
  13. local pointabilities_objects = {
  14. objects = {
  15. ["group:blocking_pointable_test"] = true,
  16. ["group:not_pointable_test"] = true,
  17. },
  18. }
  19. minetest.register_tool("testtools:param2tool", {
  20. description = S("Param2 Tool") .."\n"..
  21. S("Modify param2 value of nodes") .."\n"..
  22. S("Punch: +1") .."\n"..
  23. S("Sneak+Punch: +8") .."\n"..
  24. S("Place: -1") .."\n"..
  25. S("Sneak+Place: -8"),
  26. inventory_image = "testtools_param2tool.png",
  27. groups = { testtool = 1, disable_repair = 1 },
  28. pointabilities = pointabilities_nodes,
  29. on_use = function(itemstack, user, pointed_thing)
  30. local pos = minetest.get_pointed_thing_position(pointed_thing)
  31. if pointed_thing.type ~= "node" or (not pos) then
  32. return
  33. end
  34. local add = 1
  35. if user then
  36. local ctrl = user:get_player_control()
  37. if ctrl.sneak then
  38. add = 8
  39. end
  40. end
  41. local node = minetest.get_node(pos)
  42. node.param2 = node.param2 + add
  43. minetest.swap_node(pos, node)
  44. end,
  45. on_place = function(itemstack, user, pointed_thing)
  46. local pos = minetest.get_pointed_thing_position(pointed_thing)
  47. if pointed_thing.type ~= "node" or (not pos) then
  48. return
  49. end
  50. local add = -1
  51. if user then
  52. local ctrl = user:get_player_control()
  53. if ctrl.sneak then
  54. add = -8
  55. end
  56. end
  57. local node = minetest.get_node(pos)
  58. node.param2 = node.param2 + add
  59. minetest.swap_node(pos, node)
  60. end,
  61. })
  62. minetest.register_tool("testtools:node_setter", {
  63. description = S("Node Setter") .."\n"..
  64. S("Replace pointed node with something else") .."\n"..
  65. S("Punch: Select pointed node") .."\n"..
  66. S("Place on node: Replace node with selected node") .."\n"..
  67. S("Place in air: Manually select a node"),
  68. inventory_image = "testtools_node_setter.png",
  69. groups = { testtool = 1, disable_repair = 1 },
  70. pointabilities = pointabilities_nodes,
  71. on_use = function(itemstack, user, pointed_thing)
  72. local pos = minetest.get_pointed_thing_position(pointed_thing)
  73. if pointed_thing.type == "nothing" then
  74. local meta = itemstack:get_meta()
  75. meta:set_string("node", "air")
  76. meta:set_int("node_param2", 0)
  77. if user and user:is_player() then
  78. minetest.chat_send_player(user:get_player_name(), S("Now placing: @1 (param2=@2)", "air", 0))
  79. end
  80. return itemstack
  81. elseif pointed_thing.type ~= "node" or (not pos) then
  82. return
  83. end
  84. local node = minetest.get_node(pos)
  85. local meta = itemstack:get_meta()
  86. meta:set_string("node", node.name)
  87. meta:set_int("node_param2", node.param2)
  88. if user and user:is_player() then
  89. minetest.chat_send_player(user:get_player_name(), S("Now placing: @1 (param2=@2)", node.name, node.param2))
  90. end
  91. return itemstack
  92. end,
  93. on_secondary_use = function(itemstack, user, pointed_thing)
  94. local meta = itemstack:get_meta()
  95. local nodename = meta:get_string("node") or ""
  96. local param2 = meta:get_int("node_param2") or 0
  97. minetest.show_formspec(user:get_player_name(), "testtools:node_setter",
  98. "size[4,4]"..
  99. "field[0.5,1;3,1;nodename;"..F(S("Node name (itemstring):"))..";"..F(nodename).."]"..
  100. "field[0.5,2;3,1;param2;"..F(S("param2:"))..";"..F(tostring(param2)).."]"..
  101. "button_exit[0.5,3;3,1;submit;"..F(S("Submit")).."]"
  102. )
  103. end,
  104. on_place = function(itemstack, user, pointed_thing)
  105. local pos = minetest.get_pointed_thing_position(pointed_thing)
  106. local meta = itemstack:get_meta()
  107. local nodename = meta:get_string("node")
  108. if nodename == "" and user and user:is_player() then
  109. minetest.chat_send_player(user:get_player_name(), S("Punch a node first!"))
  110. return
  111. end
  112. local param2 = meta:get_int("node_param2")
  113. if not param2 then
  114. param2 = 0
  115. end
  116. local node = { name = nodename, param2 = param2 }
  117. if not minetest.registered_nodes[nodename] then
  118. minetest.chat_send_player(user:get_player_name(), S("Cannot set unknown node: @1", nodename))
  119. return
  120. end
  121. minetest.set_node(pos, node)
  122. end,
  123. })
  124. minetest.register_tool("testtools:remover", {
  125. description = S("Remover") .."\n"..
  126. S("Punch: Remove pointed node or object"),
  127. inventory_image = "testtools_remover.png",
  128. groups = { testtool = 1, disable_repair = 1 },
  129. pointabilities = {
  130. nodes = pointabilities_nodes.nodes,
  131. objects = pointabilities_objects.objects,
  132. },
  133. on_use = function(itemstack, user, pointed_thing)
  134. local pos = minetest.get_pointed_thing_position(pointed_thing)
  135. if pointed_thing.type == "node" and pos ~= nil then
  136. minetest.remove_node(pos)
  137. elseif pointed_thing.type == "object" then
  138. local obj = pointed_thing.ref
  139. if not obj:is_player() then
  140. obj:remove()
  141. else
  142. minetest.chat_send_player(user:get_player_name(), S("Can't remove players!"))
  143. end
  144. end
  145. end,
  146. })
  147. minetest.register_tool("testtools:falling_node_tool", {
  148. description = S("Falling Node Tool") .."\n"..
  149. S("Punch: Make pointed node fall") .."\n"..
  150. S("Place: Move pointed node 2 units upwards, then make it fall"),
  151. inventory_image = "testtools_falling_node_tool.png",
  152. groups = { testtool = 1, disable_repair = 1 },
  153. pointabilities = pointabilities_nodes,
  154. on_place = function(itemstack, user, pointed_thing)
  155. -- Teleport node 1-2 units upwards (if possible) and make it fall
  156. local pos = minetest.get_pointed_thing_position(pointed_thing)
  157. if pointed_thing.type ~= "node" or (not pos) then
  158. return
  159. end
  160. local ok = false
  161. local highest
  162. for i=1,2 do
  163. local above = {x=pos.x,y=pos.y+i,z=pos.z}
  164. local n2 = minetest.get_node(above)
  165. local def2 = minetest.registered_nodes[n2.name]
  166. if def2 and (not def2.walkable) then
  167. highest = above
  168. else
  169. break
  170. end
  171. end
  172. if highest then
  173. local node = minetest.get_node(pos)
  174. local metatable = minetest.get_meta(pos):to_table()
  175. minetest.remove_node(pos)
  176. minetest.set_node(highest, node)
  177. local meta_highest = minetest.get_meta(highest)
  178. meta_highest:from_table(metatable)
  179. ok = minetest.spawn_falling_node(highest)
  180. else
  181. ok = minetest.spawn_falling_node(pos)
  182. end
  183. if not ok and user and user:is_player() then
  184. minetest.chat_send_player(user:get_player_name(), S("Falling node could not be spawned!"))
  185. end
  186. end,
  187. on_use = function(itemstack, user, pointed_thing)
  188. local pos = minetest.get_pointed_thing_position(pointed_thing)
  189. if pointed_thing.type ~= "node" or (not pos) then
  190. return
  191. end
  192. local ok = minetest.spawn_falling_node(pos)
  193. if not ok and user and user:is_player() then
  194. minetest.chat_send_player(user:get_player_name(), S("Falling node could not be spawned!"))
  195. end
  196. end,
  197. })
  198. minetest.register_tool("testtools:rotator", {
  199. description = S("Entity Rotator") .. "\n" ..
  200. S("Rotate pointed entity") .."\n"..
  201. S("Punch: Yaw") .."\n"..
  202. S("Sneak+Punch: Pitch") .."\n"..
  203. S("Aux1+Punch: Roll"),
  204. inventory_image = "testtools_entity_rotator.png",
  205. groups = { testtool = 1, disable_repair = 1 },
  206. pointabilities = pointabilities_objects,
  207. on_use = function(itemstack, user, pointed_thing)
  208. if pointed_thing.type ~= "object" then
  209. return
  210. end
  211. local obj = pointed_thing.ref
  212. if obj:is_player() then
  213. -- No player rotation
  214. return
  215. else
  216. local axis = "y"
  217. if user and user:is_player() then
  218. local ctrl = user:get_player_control()
  219. if ctrl.sneak then
  220. axis = "x"
  221. elseif ctrl.aux1 then
  222. axis = "z"
  223. end
  224. end
  225. local rot = obj:get_rotation()
  226. rot[axis] = rot[axis] + math.pi/8
  227. if rot[axis] > math.pi*2 then
  228. rot[axis] = rot[axis] - math.pi*2
  229. end
  230. obj:set_rotation(rot)
  231. end
  232. end,
  233. })
  234. local mover_config = function(itemstack, user, pointed_thing)
  235. if not (user and user:is_player()) then
  236. return
  237. end
  238. local name = user:get_player_name()
  239. local ctrl = user:get_player_control()
  240. local meta = itemstack:get_meta()
  241. local dist = 1.0
  242. if meta:contains("distance") then
  243. dist = meta:get_int("distance")
  244. end
  245. if ctrl.sneak then
  246. dist = dist - 1
  247. else
  248. dist = dist + 1
  249. end
  250. meta:set_int("distance", dist)
  251. minetest.chat_send_player(user:get_player_name(), S("distance=@1/10", dist*2))
  252. return itemstack
  253. end
  254. minetest.register_tool("testtools:object_mover", {
  255. description = S("Object Mover") .."\n"..
  256. S("Move pointed object towards or away from you") .."\n"..
  257. S("Punch: Move by distance").."\n"..
  258. S("Sneak+Punch: Move by negative distance").."\n"..
  259. S("Place: Increase distance").."\n"..
  260. S("Sneak+Place: Decrease distance"),
  261. inventory_image = "testtools_object_mover.png",
  262. groups = { testtool = 1, disable_repair = 1 },
  263. pointabilities = pointabilities_objects,
  264. on_place = mover_config,
  265. on_secondary_use = mover_config,
  266. on_use = function(itemstack, user, pointed_thing)
  267. if pointed_thing.type ~= "object" then
  268. return
  269. end
  270. local obj = pointed_thing.ref
  271. if not (user and user:is_player()) then
  272. return
  273. end
  274. local yaw = user:get_look_horizontal()
  275. local dir = minetest.yaw_to_dir(yaw)
  276. local pos = obj:get_pos()
  277. local pitch = user:get_look_vertical()
  278. if pitch > 0.25 * math.pi then
  279. dir.y = -1
  280. dir.x = 0
  281. dir.z = 0
  282. elseif pitch < -0.25 * math.pi then
  283. dir.y = 1
  284. dir.x = 0
  285. dir.z = 0
  286. end
  287. local ctrl = user:get_player_control()
  288. if ctrl.sneak then
  289. dir = vector.multiply(dir, -1)
  290. end
  291. local meta = itemstack:get_meta()
  292. if meta:contains("distance") then
  293. local dist = meta:get_int("distance")
  294. dir = vector.multiply(dir, dist*0.2)
  295. end
  296. pos = vector.add(pos, dir)
  297. obj:set_pos(pos)
  298. end,
  299. })
  300. minetest.register_tool("testtools:entity_scaler", {
  301. description = S("Entity Visual Scaler") .."\n"..
  302. S("Scale visual size of entities") .."\n"..
  303. S("Punch: Increase size") .."\n"..
  304. S("Sneak+Punch: Decrease scale"),
  305. inventory_image = "testtools_entity_scaler.png",
  306. groups = { testtool = 1, disable_repair = 1 },
  307. pointabilities = pointabilities_objects,
  308. on_use = function(itemstack, user, pointed_thing)
  309. if pointed_thing.type ~= "object" then
  310. return
  311. end
  312. local obj = pointed_thing.ref
  313. if obj:is_player() then
  314. -- No player scaling
  315. return
  316. else
  317. local diff = 0.1
  318. if user and user:is_player() then
  319. local ctrl = user:get_player_control()
  320. if ctrl.sneak then
  321. diff = -0.1
  322. end
  323. end
  324. local prop = obj:get_properties()
  325. if not prop.visual_size then
  326. prop.visual_size = { x=1, y=1, z=1 }
  327. else
  328. prop.visual_size = { x=prop.visual_size.x+diff, y=prop.visual_size.y+diff, z=prop.visual_size.z+diff }
  329. if prop.visual_size.x <= 0.1 then
  330. prop.visual_size.x = 0.1
  331. end
  332. if prop.visual_size.y <= 0.1 then
  333. prop.visual_size.y = 0.1
  334. end
  335. if prop.visual_size.z <= 0.1 then
  336. prop.visual_size.z = 0.1
  337. end
  338. end
  339. obj:set_properties(prop)
  340. end
  341. end,
  342. })
  343. -- value-weak tables, because we don't want to keep the objrefs of unloaded objects
  344. local branded_objects = setmetatable({}, {__mode = "v"})
  345. local next_brand_num = 1
  346. function testtools.get_branded_object(name)
  347. if name:sub(1, 7) == "player:" then
  348. return minetest.get_player_by_name(name:sub(8))
  349. elseif name:sub(1, 4) == "obj:" then
  350. return branded_objects[tonumber(name:sub(5)) or 0]
  351. end
  352. return nil
  353. end
  354. minetest.register_tool("testtools:branding_iron", {
  355. description = S("Branding Iron") .."\n"..
  356. S("Give an object a temporary name.") .."\n"..
  357. S("Punch object: Brand the object") .."\n"..
  358. S("Punch air: Brand yourself") .."\n"..
  359. S("The name is valid until the object unloads.") .."\n"..
  360. S("Devices that accept the returned name also accept \"player:<playername>\" for players."),
  361. inventory_image = "testtools_branding_iron.png",
  362. groups = { testtool = 1, disable_repair = 1 },
  363. pointabilities = pointabilities_objects,
  364. on_use = function(_itemstack, user, pointed_thing)
  365. local obj
  366. local msg
  367. if pointed_thing.type == "object" then
  368. obj = pointed_thing.ref
  369. msg = "You can now refer to this object with: \"@1\""
  370. elseif pointed_thing.type == "nothing" then
  371. obj = user
  372. msg = "You can now refer to yourself with: \"@1\""
  373. else
  374. return
  375. end
  376. local brand_num = next_brand_num
  377. next_brand_num = next_brand_num + 1
  378. branded_objects[brand_num] = obj
  379. minetest.chat_send_player(user:get_player_name(), S(msg, "obj:"..brand_num))
  380. end,
  381. })
  382. local selections = {}
  383. local entity_list
  384. local function get_entity_list()
  385. if entity_list then
  386. return entity_list
  387. end
  388. local ents = minetest.registered_entities
  389. local list = {}
  390. for k,_ in pairs(ents) do
  391. table.insert(list, k)
  392. end
  393. table.sort(list)
  394. entity_list = list
  395. return entity_list
  396. end
  397. minetest.register_tool("testtools:entity_spawner", {
  398. description = S("Entity Spawner") .."\n"..
  399. S("Spawns entities") .."\n"..
  400. S("Punch: Select entity to spawn") .."\n"..
  401. S("Place: Spawn selected entity"),
  402. inventory_image = "testtools_entity_spawner.png",
  403. groups = { testtool = 1, disable_repair = 1 },
  404. on_place = function(itemstack, user, pointed_thing)
  405. local name = user:get_player_name()
  406. if pointed_thing.type == "node" then
  407. if selections[name] then
  408. local pos = pointed_thing.above
  409. minetest.add_entity(pos, get_entity_list()[selections[name]])
  410. else
  411. minetest.chat_send_player(name, S("Select an entity first (with punch key)!"))
  412. end
  413. end
  414. end,
  415. on_use = function(itemstack, user, pointed_thing)
  416. if pointed_thing.type == "object" then
  417. return
  418. end
  419. if user and user:is_player() then
  420. local list = table.concat(get_entity_list(), ",")
  421. local name = user:get_player_name()
  422. local sel = selections[name] or ""
  423. minetest.show_formspec(name, "testtools:entity_list",
  424. "size[9,9]"..
  425. "textlist[0,0;9,8;entity_list;"..list..";"..sel..";false]"..
  426. "button[0,8;4,1;spawn;Spawn entity]"
  427. )
  428. end
  429. end,
  430. })
  431. local function prop_to_string(property)
  432. if type(property) == "string" then
  433. return "\"" .. property .. "\""
  434. elseif type(property) == "table" then
  435. return tostring(dump(property)):gsub("\n", "")
  436. else
  437. return tostring(property)
  438. end
  439. end
  440. local property_formspec_data = {}
  441. local property_formspec_index = {}
  442. local selected_objects = {}
  443. local function get_object_properties_form(obj, playername)
  444. if not playername then return "" end
  445. local props = obj:get_properties()
  446. local str = ""
  447. property_formspec_data[playername] = {}
  448. local proplist = {}
  449. for k,_ in pairs(props) do
  450. table.insert(proplist, k)
  451. end
  452. table.sort(proplist)
  453. for p=1, #proplist do
  454. local k = proplist[p]
  455. local v = props[k]
  456. local newline = ""
  457. newline = k .. " = "
  458. newline = newline .. prop_to_string(v)
  459. str = str .. F(newline)
  460. if p < #proplist then
  461. str = str .. ","
  462. end
  463. table.insert(property_formspec_data[playername], k)
  464. end
  465. return str
  466. end
  467. local editor_formspec_selindex = {}
  468. local editor_formspec = function(playername, obj, value, sel)
  469. if not value then
  470. value = ""
  471. end
  472. if not sel then
  473. sel = ""
  474. end
  475. local list = get_object_properties_form(obj, playername)
  476. local title
  477. if obj:is_player() then
  478. title = S("Object properties of player “@1”", obj:get_player_name())
  479. else
  480. local ent = obj:get_luaentity()
  481. title = S("Object properties of @1", ent.name)
  482. end
  483. minetest.show_formspec(playername, "testtools:object_editor",
  484. "size[9,9]"..
  485. "label[0,0;"..F(title).."]"..
  486. "textlist[0,0.5;9,7.5;object_props;"..list..";"..sel..";false]"..
  487. "field[0.2,8.75;8,1;value;"..F(S("Value"))..";"..F(value).."]"..
  488. "field_close_on_enter[value;false]"..
  489. "button[8,8.5;1,1;submit;"..F(S("Submit")).."]"
  490. )
  491. end
  492. minetest.register_tool("testtools:object_editor", {
  493. description = S("Object Property Editor") .."\n"..
  494. S("Edit properties of objects") .."\n"..
  495. S("Punch object: Edit object") .."\n"..
  496. S("Punch air: Edit yourself"),
  497. inventory_image = "testtools_object_editor.png",
  498. groups = { testtool = 1, disable_repair = 1 },
  499. pointabilities = pointabilities_objects,
  500. on_use = function(itemstack, user, pointed_thing)
  501. if user and user:is_player() then
  502. local name = user:get_player_name()
  503. if pointed_thing.type == "object" then
  504. selected_objects[name] = pointed_thing.ref
  505. elseif pointed_thing.type == "nothing" then
  506. -- Use on yourself if pointing nothing
  507. selected_objects[name] = user
  508. else
  509. -- Unsupported pointed thing
  510. return
  511. end
  512. local sel = editor_formspec_selindex[name]
  513. local val
  514. if selected_objects[name] and selected_objects[name]:get_properties() then
  515. local props = selected_objects[name]:get_properties()
  516. local keys = property_formspec_data[name]
  517. if property_formspec_index[name] and props then
  518. local key = keys[property_formspec_index[name]]
  519. val = prop_to_string(props[key])
  520. end
  521. end
  522. editor_formspec(name, selected_objects[name], val, sel)
  523. end
  524. end,
  525. })
  526. local ent_parent = {}
  527. local ent_child = {}
  528. local DEFAULT_ATTACH_OFFSET_Y = 11
  529. local attacher_config = function(itemstack, user, pointed_thing)
  530. if not (user and user:is_player()) then
  531. return
  532. end
  533. if pointed_thing.type == "object" then
  534. return
  535. end
  536. local name = user:get_player_name()
  537. local ctrl = user:get_player_control()
  538. local meta = itemstack:get_meta()
  539. if ctrl.aux1 then
  540. local rot_x = meta:get_float("rot_x")
  541. if ctrl.sneak then
  542. rot_x = rot_x - math.pi/8
  543. else
  544. rot_x = rot_x + math.pi/8
  545. end
  546. if rot_x > 6.2 then
  547. rot_x = 0
  548. elseif rot_x < 0 then
  549. rot_x = math.pi * (15/8)
  550. end
  551. minetest.chat_send_player(name, S("rotation=@1", minetest.pos_to_string({x=rot_x,y=0,z=0})))
  552. meta:set_float("rot_x", rot_x)
  553. else
  554. local pos_y
  555. if meta:contains("pos_y") then
  556. pos_y = meta:get_int("pos_y")
  557. else
  558. pos_y = DEFAULT_ATTACH_OFFSET_Y
  559. end
  560. if ctrl.sneak then
  561. pos_y = pos_y - 1
  562. else
  563. pos_y = pos_y + 1
  564. end
  565. minetest.chat_send_player(name, S("position=@1", minetest.pos_to_string({x=0,y=pos_y,z=0})))
  566. meta:set_int("pos_y", pos_y)
  567. end
  568. return itemstack
  569. end
  570. minetest.register_tool("testtools:object_attacher", {
  571. description = S("Object Attacher") .."\n"..
  572. S("Attach object to another") .."\n"..
  573. S("Punch objects to first select parent object, then the child object to attach") .."\n"..
  574. S("Punch air to select yourself") .."\n"..
  575. S("Place: Incease attachment Y offset") .."\n"..
  576. S("Sneak+Place: Decease attachment Y offset") .."\n"..
  577. S("Aux1+Place: Incease attachment rotation") .."\n"..
  578. S("Aux1+Sneak+Place: Decrease attachment rotation"),
  579. inventory_image = "testtools_object_attacher.png",
  580. groups = { testtool = 1, disable_repair = 1 },
  581. pointabilities = pointabilities_objects,
  582. on_place = attacher_config,
  583. on_secondary_use = attacher_config,
  584. on_use = function(itemstack, user, pointed_thing)
  585. if user and user:is_player() then
  586. local name = user:get_player_name()
  587. local selected_object
  588. if pointed_thing.type == "object" then
  589. selected_object = pointed_thing.ref
  590. elseif pointed_thing.type == "nothing" then
  591. selected_object = user
  592. else
  593. return
  594. end
  595. local ctrl = user:get_player_control()
  596. if ctrl.sneak then
  597. if selected_object:get_attach() then
  598. selected_object:set_detach()
  599. minetest.chat_send_player(name, S("Object detached!"))
  600. else
  601. minetest.chat_send_player(name, S("Object is not attached!"))
  602. end
  603. return
  604. end
  605. local parent = ent_parent[name]
  606. local child = ent_child[name]
  607. local ename = S("<unknown>")
  608. if not parent then
  609. parent = selected_object
  610. ent_parent[name] = parent
  611. elseif not child then
  612. child = selected_object
  613. ent_child[name] = child
  614. end
  615. local entity = selected_object:get_luaentity()
  616. if entity then
  617. ename = entity.name
  618. elseif selected_object:is_player() then
  619. ename = selected_object:get_player_name()
  620. end
  621. if selected_object == parent then
  622. minetest.chat_send_player(name, S("Parent object selected: @1", ename))
  623. elseif selected_object == child then
  624. minetest.chat_send_player(name, S("Child object selected: @1", ename))
  625. end
  626. if parent and child then
  627. if parent == child then
  628. minetest.chat_send_player(name, S("Can't attach an object to itself!"))
  629. ent_parent[name] = nil
  630. ent_child[name] = nil
  631. return
  632. end
  633. local meta = itemstack:get_meta()
  634. local y
  635. if meta:contains("pos_y") then
  636. y = meta:get_int("pos_y")
  637. else
  638. y = DEFAULT_ATTACH_OFFSET_Y
  639. end
  640. local rx = meta:get_float("rot_x") or 0
  641. local offset = {x=0,y=y,z=0}
  642. local angle = {x=rx,y=0,z=0}
  643. child:set_attach(parent, "", offset, angle)
  644. local check_parent = child:get_attach()
  645. if check_parent then
  646. minetest.chat_send_player(name, S("Object attached! position=@1, rotation=@2",
  647. minetest.pos_to_string(offset), minetest.pos_to_string(angle)))
  648. else
  649. minetest.chat_send_player(name, S("Attachment failed!"))
  650. end
  651. ent_parent[name] = nil
  652. ent_child[name] = nil
  653. end
  654. end
  655. end,
  656. })
  657. local function print_object(obj)
  658. if obj:is_player() then
  659. return "player '"..obj:get_player_name().."'"
  660. elseif obj:get_luaentity() then
  661. return "LuaEntity '"..obj:get_luaentity().name.."'"
  662. else
  663. return "object"
  664. end
  665. end
  666. minetest.register_tool("testtools:children_getter", {
  667. description = S("Children Getter") .."\n"..
  668. S("Shows list of objects attached to object") .."\n"..
  669. S("Punch object to show its 'children'") .."\n"..
  670. S("Punch air to show your own 'children'"),
  671. inventory_image = "testtools_children_getter.png",
  672. groups = { testtool = 1, disable_repair = 1 },
  673. pointabilities = pointabilities_objects,
  674. on_use = function(itemstack, user, pointed_thing)
  675. if user and user:is_player() then
  676. local name = user:get_player_name()
  677. local selected_object
  678. local self_name
  679. if pointed_thing.type == "object" then
  680. selected_object = pointed_thing.ref
  681. elseif pointed_thing.type == "nothing" then
  682. selected_object = user
  683. else
  684. return
  685. end
  686. self_name = print_object(selected_object)
  687. local children = selected_object:get_children()
  688. local ret = ""
  689. for c=1, #children do
  690. ret = ret .. "* " .. print_object(children[c])
  691. if c < #children then
  692. ret = ret .. "\n"
  693. end
  694. end
  695. if ret == "" then
  696. ret = S("No children attached to @1.", self_name)
  697. else
  698. ret = S("Children of @1:", self_name) .. "\n" .. ret
  699. end
  700. minetest.chat_send_player(user:get_player_name(), ret)
  701. end
  702. end,
  703. })
  704. -- Use loadstring to parse param as a Lua value
  705. local function use_loadstring(param, player)
  706. -- For security reasons, require 'server' priv, just in case
  707. -- someone is actually crazy enough to run this on a public server.
  708. local privs = minetest.get_player_privs(player:get_player_name())
  709. if not privs.server then
  710. return false, "You need 'server' privilege to change object properties!"
  711. end
  712. if not param then
  713. return false, "Failed: parameter is nil"
  714. end
  715. --[[ DANGER ZONE ]]
  716. -- Interpret string as Lua value
  717. local func, errormsg = loadstring("return (" .. param .. ")")
  718. if not func then
  719. return false, "Failed: " .. errormsg
  720. end
  721. -- Apply sandbox here using setfenv
  722. setfenv(func, {})
  723. -- Run it
  724. local good, errOrResult = pcall(func)
  725. if not good then
  726. -- A Lua error was thrown
  727. return false, "Failed: " .. errOrResult
  728. end
  729. -- errOrResult will be the value
  730. return true, errOrResult
  731. end
  732. -- Item Meta Editor + Node Meta Editor
  733. local node_meta_posses = {}
  734. local meta_latest_keylist = {}
  735. local function show_meta_formspec(user, metatype, pos_or_item, key, value, keylist)
  736. local textlist
  737. if keylist then
  738. textlist = "textlist[0,0.5;2.5,6.5;keylist;"..keylist.."]"
  739. else
  740. textlist = ""
  741. end
  742. local form = "size[15,9]"..
  743. "label[0,0;"..F(S("Current keys:")).."]"..
  744. textlist..
  745. "field[3,0.5;12,1;key;"..F(S("Key"))..";"..F(key).."]"..
  746. "textarea[3,1.5;12,6;value;"..F(S("Value (use empty value to delete key)"))..";"..F(value).."]"..
  747. "button[4,8;3,1;set;"..F(S("Set value")).."]"
  748. local extra_label
  749. local formname
  750. if metatype == "node" then
  751. formname = "testtools:node_meta_editor"
  752. extra_label = S("pos = @1", minetest.pos_to_string(pos_or_item))
  753. else
  754. formname = "testtools:item_meta_editor"
  755. extra_label = S("item = @1", pos_or_item:get_name())
  756. end
  757. form = form .. "label[0,7.2;"..F(extra_label).."]"
  758. minetest.show_formspec(user:get_player_name(), formname, form)
  759. end
  760. local function get_meta_keylist(meta, playername, escaped)
  761. local keys = {}
  762. local ekeys = {}
  763. local mtable = meta:to_table()
  764. for k,_ in pairs(mtable.fields) do
  765. table.insert(keys, k)
  766. if escaped then
  767. table.insert(ekeys, F(k))
  768. else
  769. table.insert(ekeys, k)
  770. end
  771. end
  772. if playername then
  773. meta_latest_keylist[playername] = keys
  774. end
  775. return table.concat(ekeys, ",")
  776. end
  777. minetest.register_tool("testtools:node_meta_editor", {
  778. description = S("Node Meta Editor") .. "\n" ..
  779. S("Place: Edit node metadata"),
  780. inventory_image = "testtools_node_meta_editor.png",
  781. groups = { testtool = 1, disable_repair = 1 },
  782. on_place = function(itemstack, user, pointed_thing)
  783. if pointed_thing.type ~= "node" then
  784. return itemstack
  785. end
  786. if not user:is_player() then
  787. return itemstack
  788. end
  789. local pos = pointed_thing.under
  790. node_meta_posses[user:get_player_name()] = pos
  791. local meta = minetest.get_meta(pos)
  792. local inv = meta:get_inventory()
  793. show_meta_formspec(user, "node", pos, "", "", get_meta_keylist(meta, user:get_player_name(), true))
  794. return itemstack
  795. end,
  796. })
  797. local function get_item_next_to_wielded_item(player)
  798. local inv = player:get_inventory()
  799. local wield = player:get_wield_index()
  800. local itemstack = inv:get_stack("main", wield+1)
  801. return itemstack
  802. end
  803. local function set_item_next_to_wielded_item(player, itemstack)
  804. local inv = player:get_inventory()
  805. local wield = player:get_wield_index()
  806. inv:set_stack("main", wield+1, itemstack)
  807. end
  808. local function use_item_meta_editor(itemstack, user, pointed_thing)
  809. if not user:is_player() then
  810. return itemstack
  811. end
  812. local item_to_edit = get_item_next_to_wielded_item(user)
  813. if item_to_edit:is_empty() then
  814. minetest.chat_send_player(user:get_player_name(), S("Place an item next to the Item Meta Editor in your inventory first!"))
  815. return itemstack
  816. end
  817. local meta = item_to_edit:get_meta()
  818. show_meta_formspec(user, "item", item_to_edit, "", "", get_meta_keylist(meta, user:get_player_name(), true))
  819. return itemstack
  820. end
  821. minetest.register_tool("testtools:item_meta_editor", {
  822. description = S("Item Meta Editor") .. "\n" ..
  823. S("Punch/Place: Edit item metadata of item in the next inventory slot"),
  824. inventory_image = "testtools_item_meta_editor.png",
  825. groups = { testtool = 1, disable_repair = 1 },
  826. on_use = use_item_meta_editor,
  827. on_secondary_use = use_item_meta_editor,
  828. on_place = use_item_meta_editor,
  829. })
  830. minetest.register_on_player_receive_fields(function(player, formname, fields)
  831. if not (player and player:is_player()) then
  832. return
  833. end
  834. if formname == "testtools:entity_list" then
  835. local name = player:get_player_name()
  836. if fields.entity_list then
  837. local expl = minetest.explode_textlist_event(fields.entity_list)
  838. if expl.type == "DCL" then
  839. local pos = vector.add(player:get_pos(), {x=0,y=1,z=0})
  840. selections[name] = expl.index
  841. minetest.add_entity(pos, get_entity_list()[expl.index])
  842. return
  843. elseif expl.type == "CHG" then
  844. selections[name] = expl.index
  845. return
  846. end
  847. elseif fields.spawn and selections[name] then
  848. local pos = vector.add(player:get_pos(), {x=0,y=1,z=0})
  849. minetest.add_entity(pos, get_entity_list()[selections[name]])
  850. return
  851. end
  852. elseif formname == "testtools:object_editor" then
  853. local name = player:get_player_name()
  854. if fields.object_props then
  855. local expl = minetest.explode_textlist_event(fields.object_props)
  856. if expl.type == "DCL" or expl.type == "CHG" then
  857. property_formspec_index[name] = expl.index
  858. local props = selected_objects[name]:get_properties()
  859. local keys = property_formspec_data[name]
  860. if (not property_formspec_index[name]) or (not props) then
  861. return
  862. end
  863. local key = keys[property_formspec_index[name]]
  864. editor_formspec_selindex[name] = expl.index
  865. editor_formspec(name, selected_objects[name], prop_to_string(props[key]), expl.index)
  866. return
  867. end
  868. end
  869. if fields.key_enter_field == "value" or fields.submit then
  870. local props = selected_objects[name]:get_properties()
  871. local keys = property_formspec_data[name]
  872. if (not property_formspec_index[name]) or (not props) then
  873. return
  874. end
  875. local key = keys[property_formspec_index[name]]
  876. if not key then
  877. return
  878. end
  879. local success, str = use_loadstring(fields.value, player)
  880. if success then
  881. props[key] = str
  882. else
  883. minetest.chat_send_player(name, str)
  884. return
  885. end
  886. selected_objects[name]:set_properties(props)
  887. local sel = editor_formspec_selindex[name]
  888. editor_formspec(name, selected_objects[name], prop_to_string(props[key]), sel)
  889. return
  890. end
  891. elseif formname == "testtools:node_setter" then
  892. local playername = player:get_player_name()
  893. local witem = player:get_wielded_item()
  894. if witem:get_name() == "testtools:node_setter" then
  895. if fields.nodename and fields.param2 then
  896. local param2 = tonumber(fields.param2)
  897. if not param2 then
  898. return
  899. end
  900. local meta = witem:get_meta()
  901. meta:set_string("node", fields.nodename)
  902. meta:set_int("node_param2", param2)
  903. player:set_wielded_item(witem)
  904. end
  905. end
  906. elseif formname == "testtools:node_meta_editor" or formname == "testtools:item_meta_editor" then
  907. local name = player:get_player_name()
  908. local metatype
  909. local pos_or_item
  910. if formname == "testtools:node_meta_editor" then
  911. metatype = "node"
  912. pos_or_item = node_meta_posses[name]
  913. else
  914. metatype = "item"
  915. pos_or_item = get_item_next_to_wielded_item(player)
  916. end
  917. if fields.keylist then
  918. local evnt = minetest.explode_textlist_event(fields.keylist)
  919. if evnt.type == "DCL" or evnt.type == "CHG" then
  920. local keylist_table = meta_latest_keylist[name]
  921. if metatype == "node" and not pos_or_item then
  922. return
  923. end
  924. local meta
  925. if metatype == "node" then
  926. meta = minetest.get_meta(pos_or_item)
  927. else
  928. meta = pos_or_item:get_meta()
  929. end
  930. if not keylist_table then
  931. return
  932. end
  933. if #keylist_table == 0 then
  934. return
  935. end
  936. local key = keylist_table[evnt.index]
  937. local value = meta:get_string(key)
  938. local keylist_escaped = {}
  939. for k,v in pairs(keylist_table) do
  940. keylist_escaped[k] = F(v)
  941. end
  942. local keylist = table.concat(keylist_escaped, ",")
  943. show_meta_formspec(player, metatype, pos_or_item, key, value, keylist)
  944. return
  945. end
  946. elseif fields.key and fields.key ~= "" and fields.value then
  947. if metatype == "node" and not pos_or_item then
  948. return
  949. end
  950. local meta
  951. if metatype == "node" then
  952. meta = minetest.get_meta(pos_or_item)
  953. elseif metatype == "item" then
  954. if pos_or_item:is_empty() then
  955. return
  956. end
  957. meta = pos_or_item:get_meta()
  958. end
  959. if fields.set then
  960. meta:set_string(fields.key, fields.value)
  961. if metatype == "item" then
  962. set_item_next_to_wielded_item(player, pos_or_item)
  963. end
  964. show_meta_formspec(player, metatype, pos_or_item, fields.key, fields.value,
  965. get_meta_keylist(meta, name, true))
  966. end
  967. return
  968. end
  969. end
  970. end)
  971. minetest.register_on_leaveplayer(function(player)
  972. local name = player:get_player_name()
  973. meta_latest_keylist[name] = nil
  974. node_meta_posses[name] = nil
  975. end)
  976. -- Pointing Staffs
  977. minetest.register_tool("testtools:blocked_pointing_staff", {
  978. description = S("Blocked Pointing Staff").."\n"..
  979. S("Can point the Blocking Pointable Node/Object and "..
  980. "the Pointable Node/Object is point blocking."),
  981. inventory_image = "testtools_blocked_pointing_staff.png",
  982. pointabilities = {
  983. nodes = {
  984. ["testnodes:blocking_pointable"] = true,
  985. ["group:pointable_test"] = "blocking"
  986. },
  987. objects = {
  988. ["testentities:blocking_pointable"] = true,
  989. ["group:pointable_test"] = "blocking"
  990. }
  991. }
  992. })
  993. minetest.register_tool("testtools:ultimate_pointing_staff", {
  994. description = S("Ultimate Pointing Staff").."\n"..
  995. S("Can point all pointable test nodes, objects and liquids."),
  996. inventory_image = "testtools_ultimate_pointing_staff.png",
  997. liquids_pointable = true,
  998. pointabilities = {
  999. nodes = {
  1000. ["group:blocking_pointable_test"] = true,
  1001. ["group:pointable_test"] = true,
  1002. ["testnodes:not_pointable"] = true
  1003. },
  1004. objects = {
  1005. ["group:blocking_pointable_test"] = true,
  1006. ["group:pointable_test"] = true,
  1007. ["testentities:not_pointable"] = true
  1008. }
  1009. }
  1010. })