init.lua 31 KB

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