1 local S = minetest.get_translator("testtools")
2 local F = minetest.formspec_escape
4 -- TODO: Add a Node Metadata tool
6 -- Param 2 Tool: Set param2 value of tools
11 minetest.register_tool("testtools:param2tool", {
12 description = S("Param2 Tool"),
13 inventory_image = "testtools_param2tool.png",
14 groups = { testtool = 1, disable_repair = 1 },
15 on_use = function(itemstack, user, pointed_thing)
16 local pos = minetest.get_pointed_thing_position(pointed_thing)
17 if pointed_thing.type ~= "node" or (not pos) then
22 local ctrl = user:get_player_control()
27 local node = minetest.get_node(pos)
28 node.param2 = node.param2 + add
29 minetest.swap_node(pos, node)
31 on_place = function(itemstack, user, pointed_thing)
32 local pos = minetest.get_pointed_thing_position(pointed_thing)
33 if pointed_thing.type ~= "node" or (not pos) then
38 local ctrl = user:get_player_control()
43 local node = minetest.get_node(pos)
44 node.param2 = node.param2 + add
45 minetest.swap_node(pos, node)
49 minetest.register_tool("testtools:node_setter", {
50 description = S("Node Setter"),
51 inventory_image = "testtools_node_setter.png",
52 groups = { testtool = 1, disable_repair = 1 },
53 on_use = function(itemstack, user, pointed_thing)
54 local pos = minetest.get_pointed_thing_position(pointed_thing)
55 if pointed_thing.type == "nothing" then
56 local meta = itemstack:get_meta()
57 meta:set_string("node", "air")
58 meta:set_int("node_param2", 0)
59 if user and user:is_player() then
60 minetest.chat_send_player(user:get_player_name(), S("Now placing: @1 (param2=@2)", "air", 0))
63 elseif pointed_thing.type ~= "node" or (not pos) then
66 local node = minetest.get_node(pos)
67 local meta = itemstack:get_meta()
68 meta:set_string("node", node.name)
69 meta:set_int("node_param2", node.param2)
70 if user and user:is_player() then
71 minetest.chat_send_player(user:get_player_name(), S("Now placing: @1 (param2=@2)", node.name, node.param2))
75 on_secondary_use = function(itemstack, user, pointed_thing)
76 local meta = itemstack:get_meta()
77 local nodename = meta:get_string("node") or ""
78 local param2 = meta:get_int("node_param2") or 0
80 minetest.show_formspec(user:get_player_name(), "testtools:node_setter",
82 "field[0.5,1;3,1;nodename;"..F(S("Node name (itemstring):"))..";"..F(nodename).."]"..
83 "field[0.5,2;3,1;param2;"..F(S("param2:"))..";"..F(tostring(param2)).."]"..
84 "button_exit[0.5,3;3,1;submit;"..F(S("Submit")).."]"
87 on_place = function(itemstack, user, pointed_thing)
88 local pos = minetest.get_pointed_thing_position(pointed_thing)
89 local meta = itemstack:get_meta()
90 local nodename = meta:get_string("node")
91 if nodename == "" and user and user:is_player() then
92 minetest.chat_send_player(user:get_player_name(), S("Punch a node first!"))
95 local param2 = meta:get_int("node_param2")
99 local node = { name = nodename, param2 = param2 }
100 if not minetest.registered_nodes[nodename] then
101 minetest.chat_send_player(user:get_player_name(), S("Cannot set unknown node: @1", nodename))
104 minetest.set_node(pos, node)
108 minetest.register_on_player_receive_fields(function(player, formname, fields)
109 if formname == "testtools:node_setter" then
110 local playername = player:get_player_name()
111 local witem = player:get_wielded_item()
112 if witem:get_name() == "testtools:node_setter" then
113 if fields.nodename and fields.param2 then
114 local param2 = tonumber(fields.param2)
118 local meta = witem:get_meta()
119 meta:set_string("node", fields.nodename)
120 meta:set_int("node_param2", param2)
121 player:set_wielded_item(witem)
127 minetest.register_tool("testtools:remover", {
128 description = S("Remover"),
129 inventory_image = "testtools_remover.png",
130 groups = { testtool = 1, disable_repair = 1 },
131 on_use = function(itemstack, user, pointed_thing)
132 local pos = minetest.get_pointed_thing_position(pointed_thing)
133 if pointed_thing.type == "node" and pos ~= nil then
134 minetest.remove_node(pos)
135 elseif pointed_thing.type == "object" then
136 local obj = pointed_thing.ref
137 if not obj:is_player() then
144 minetest.register_tool("testtools:falling_node_tool", {
145 description = S("Falling Node Tool"),
146 inventory_image = "testtools_falling_node_tool.png",
147 groups = { testtool = 1, disable_repair = 1 },
148 on_place = function(itemstack, user, pointed_thing)
149 -- Teleport node 1-2 units upwards (if possible) and make it fall
150 local pos = minetest.get_pointed_thing_position(pointed_thing)
151 if pointed_thing.type ~= "node" or (not pos) then
157 local above = {x=pos.x,y=pos.y+i,z=pos.z}
158 local n2 = minetest.get_node(above)
159 local def2 = minetest.registered_nodes[n2.name]
160 if def2 and (not def2.walkable) then
167 local node = minetest.get_node(pos)
168 local metatable = minetest.get_meta(pos):to_table()
169 minetest.remove_node(pos)
170 minetest.set_node(highest, node)
171 local meta_highest = minetest.get_meta(highest)
172 meta_highest:from_table(metatable)
173 ok = minetest.spawn_falling_node(highest)
175 ok = minetest.spawn_falling_node(pos)
177 if not ok and user and user:is_player() then
178 minetest.chat_send_player(user:get_player_name(), S("Falling node could not be spawned!"))
181 on_use = function(itemstack, user, pointed_thing)
182 local pos = minetest.get_pointed_thing_position(pointed_thing)
183 if pointed_thing.type ~= "node" or (not pos) then
186 local ok = minetest.spawn_falling_node(pos)
187 if not ok and user and user:is_player() then
188 minetest.chat_send_player(user:get_player_name(), S("Falling node could not be spawned!"))
193 minetest.register_tool("testtools:rotator", {
194 description = S("Entity Rotator"),
195 inventory_image = "testtools_entity_rotator.png",
196 groups = { testtool = 1, disable_repair = 1 },
197 on_use = function(itemstack, user, pointed_thing)
198 if pointed_thing.type ~= "object" then
201 local obj = pointed_thing.ref
202 if obj:is_player() then
203 -- No player rotation
207 if user and user:is_player() then
208 local ctrl = user:get_player_control()
211 elseif ctrl.aux1 then
215 local rot = obj:get_rotation()
216 rot[axis] = rot[axis] + math.pi/8
217 if rot[axis] > math.pi*2 then
218 rot[axis] = rot[axis] - math.pi*2
220 obj:set_rotation(rot)
225 local mover_config = function(itemstack, user, pointed_thing)
226 if not (user and user:is_player()) then
229 local name = user:get_player_name()
230 local ctrl = user:get_player_control()
231 local meta = itemstack:get_meta()
233 if meta:contains("distance") then
234 dist = meta:get_int("distance")
241 meta:set_int("distance", dist)
242 minetest.chat_send_player(user:get_player_name(), S("distance=@1/10", dist*2))
246 minetest.register_tool("testtools:object_mover", {
247 description = S("Object Mover"),
248 inventory_image = "testtools_object_mover.png",
249 groups = { testtool = 1, disable_repair = 1 },
250 on_place = mover_config,
251 on_secondary_use = mover_config,
252 on_use = function(itemstack, user, pointed_thing)
253 if pointed_thing.type ~= "object" then
256 local obj = pointed_thing.ref
257 if not (user and user:is_player()) then
260 local yaw = user:get_look_horizontal()
261 local dir = minetest.yaw_to_dir(yaw)
262 local pos = obj:get_pos()
263 local pitch = user:get_look_vertical()
264 if pitch > 0.25 * math.pi then
268 elseif pitch < -0.25 * math.pi then
273 local ctrl = user:get_player_control()
275 dir = vector.multiply(dir, -1)
277 local meta = itemstack:get_meta()
278 if meta:contains("distance") then
279 local dist = meta:get_int("distance")
280 dir = vector.multiply(dir, dist*0.2)
282 pos = vector.add(pos, dir)
289 minetest.register_tool("testtools:entity_scaler", {
290 description = S("Entity Visual Scaler"),
291 inventory_image = "testtools_entity_scaler.png",
292 groups = { testtool = 1, disable_repair = 1 },
293 on_use = function(itemstack, user, pointed_thing)
294 if pointed_thing.type ~= "object" then
297 local obj = pointed_thing.ref
298 if obj:is_player() then
303 if user and user:is_player() then
304 local ctrl = user:get_player_control()
309 local prop = obj:get_properties()
310 if not prop.visual_size then
311 prop.visual_size = { x=1, y=1, z=1 }
313 prop.visual_size = { x=prop.visual_size.x+diff, y=prop.visual_size.y+diff, z=prop.visual_size.z+diff }
314 if prop.visual_size.x <= 0.1 then
315 prop.visual_size.x = 0.1
317 if prop.visual_size.y <= 0.1 then
318 prop.visual_size.y = 0.1
320 if prop.visual_size.z <= 0.1 then
321 prop.visual_size.z = 0.1
324 obj:set_properties(prop)
329 local selections = {}
331 local function get_entity_list()
335 local ents = minetest.registered_entities
337 for k,_ in pairs(ents) do
338 table.insert(list, k)
344 minetest.register_tool("testtools:entity_spawner", {
345 description = S("Entity Spawner"),
346 inventory_image = "testtools_entity_spawner.png",
347 groups = { testtool = 1, disable_repair = 1 },
348 on_place = function(itemstack, user, pointed_thing)
349 local name = user:get_player_name()
350 if selections[name] and pointed_thing.type == "node" then
351 local pos = pointed_thing.above
352 minetest.add_entity(pos, get_entity_list()[selections[name]])
355 on_use = function(itemstack, user, pointed_thing)
356 if pointed_thing.type == "object" then
359 if user and user:is_player() then
360 local list = table.concat(get_entity_list(), ",")
361 local name = user:get_player_name()
362 local sel = selections[name] or ""
363 minetest.show_formspec(name, "testtools:entity_list",
365 "textlist[0,0;9,8;entity_list;"..list..";"..sel..";false]"..
366 "button[0,8;4,1;spawn;Spawn entity]"
372 local function prop_to_string(property)
373 if type(property) == "string" then
374 return "\"" .. property .. "\""
375 elseif type(property) == "table" then
376 return tostring(dump(property)):gsub("\n", "")
378 return tostring(property)
382 local property_formspec_data = {}
383 local property_formspec_index = {}
384 local selected_objects = {}
385 local function get_object_properties_form(obj, playername)
386 if not playername then return "" end
387 local props = obj:get_properties()
389 property_formspec_data[playername] = {}
391 for k,_ in pairs(props) do
392 table.insert(proplist, k)
395 for p=1, #proplist do
396 local k = proplist[p]
400 newline = newline .. prop_to_string(v)
401 str = str .. F(newline)
402 if p < #proplist then
405 table.insert(property_formspec_data[playername], k)
410 local editor_formspec_selindex = {}
412 local editor_formspec = function(playername, obj, value, sel)
419 local list = get_object_properties_form(obj, playername)
421 if obj:is_player() then
422 title = S("Object properties of player “@1”", obj:get_player_name())
424 local ent = obj:get_luaentity()
425 title = S("Object properties of @1", ent.name)
427 minetest.show_formspec(playername, "testtools:object_editor",
429 "label[0,0;"..F(title).."]"..
430 "textlist[0,0.5;9,7.5;object_props;"..list..";"..sel..";false]"..
431 "field[0.2,8.75;8,1;value;"..F(S("Value"))..";"..F(value).."]"..
432 "field_close_on_enter[value;false]"..
433 "button[8,8.5;1,1;submit;"..F(S("Submit")).."]"
437 minetest.register_tool("testtools:object_editor", {
438 description = S("Object Property Editor"),
439 inventory_image = "testtools_object_editor.png",
440 groups = { testtool = 1, disable_repair = 1 },
441 on_use = function(itemstack, user, pointed_thing)
442 if user and user:is_player() then
443 local name = user:get_player_name()
445 if pointed_thing.type == "object" then
446 selected_objects[name] = pointed_thing.ref
447 elseif pointed_thing.type == "nothing" then
448 -- Use on yourself if pointing nothing
449 selected_objects[name] = user
451 -- Unsupported pointed thing
455 local sel = editor_formspec_selindex[name]
457 if selected_objects[name] and selected_objects[name]:get_properties() then
458 local props = selected_objects[name]:get_properties()
459 local keys = property_formspec_data[name]
460 if property_formspec_index[name] and props then
461 local key = keys[property_formspec_index[name]]
462 val = prop_to_string(props[key])
466 editor_formspec(name, selected_objects[name], val, sel)
471 local ent_parent = {}
473 local DEFAULT_ATTACH_OFFSET_Y = 11
475 local attacher_config = function(itemstack, user, pointed_thing)
476 if not (user and user:is_player()) then
479 if pointed_thing.type == "object" then
482 local name = user:get_player_name()
483 local ctrl = user:get_player_control()
484 local meta = itemstack:get_meta()
486 local rot_x = meta:get_float("rot_x")
488 rot_x = rot_x - math.pi/8
490 rot_x = rot_x + math.pi/8
494 elseif rot_x < 0 then
495 rot_x = math.pi * (15/8)
497 minetest.chat_send_player(name, S("rotation=@1", minetest.pos_to_string({x=rot_x,y=0,z=0})))
498 meta:set_float("rot_x", rot_x)
501 if meta:contains("pos_y") then
502 pos_y = meta:get_int("pos_y")
504 pos_y = DEFAULT_ATTACH_OFFSET_Y
511 minetest.chat_send_player(name, S("position=@1", minetest.pos_to_string({x=0,y=pos_y,z=0})))
512 meta:set_int("pos_y", pos_y)
517 minetest.register_tool("testtools:object_attacher", {
518 description = S("Object Attacher"),
519 inventory_image = "testtools_object_attacher.png",
520 groups = { testtool = 1, disable_repair = 1 },
521 on_place = attacher_config,
522 on_secondary_use = attacher_config,
523 on_use = function(itemstack, user, pointed_thing)
524 if user and user:is_player() then
525 local name = user:get_player_name()
526 local selected_object
527 if pointed_thing.type == "object" then
528 selected_object = pointed_thing.ref
529 elseif pointed_thing.type == "nothing" then
530 selected_object = user
534 local ctrl = user:get_player_control()
536 if selected_object:get_attach() then
537 selected_object:set_detach()
538 minetest.chat_send_player(name, S("Object detached!"))
540 minetest.chat_send_player(name, S("Object is not attached!"))
544 local parent = ent_parent[name]
545 local child = ent_child[name]
546 local ename = S("<unknown>")
548 parent = selected_object
549 ent_parent[name] = parent
550 elseif not child then
551 child = selected_object
552 ent_child[name] = child
554 local entity = selected_object:get_luaentity()
557 elseif selected_object:is_player() then
558 ename = selected_object:get_player_name()
560 if selected_object == parent then
561 minetest.chat_send_player(name, S("Parent object selected: @1", ename))
562 elseif selected_object == child then
563 minetest.chat_send_player(name, S("Child object selected: @1", ename))
565 if parent and child then
566 if parent == child then
567 minetest.chat_send_player(name, S("Can't attach an object to itself!"))
568 ent_parent[name] = nil
569 ent_child[name] = nil
572 local meta = itemstack:get_meta()
574 if meta:contains("pos_y") then
575 y = meta:get_int("pos_y")
577 y = DEFAULT_ATTACH_OFFSET_Y
579 local rx = meta:get_float("rot_x") or 0
580 local offset = {x=0,y=y,z=0}
581 local angle = {x=rx,y=0,z=0}
582 child:set_attach(parent, "", offset, angle)
583 local check_parent = child:get_attach()
585 minetest.chat_send_player(name, S("Object attached! position=@1, rotation=@2",
586 minetest.pos_to_string(offset), minetest.pos_to_string(angle)))
588 minetest.chat_send_player(name, S("Attachment failed!"))
590 ent_parent[name] = nil
591 ent_child[name] = nil
597 -- Use loadstring to parse param as a Lua value
598 local function use_loadstring(param, player)
599 -- For security reasons, require 'server' priv, just in case
600 -- someone is actually crazy enough to run this on a public server.
601 local privs = minetest.get_player_privs(player:get_player_name())
602 if not privs.server then
603 return false, "You need 'server' privilege to change object properties!"
606 return false, "Failed: parameter is nil"
609 -- Interpret string as Lua value
610 local func, errormsg = loadstring("return (" .. param .. ")")
612 return false, "Failed: " .. errormsg
615 -- Apply sandbox here using setfenv
619 local good, errOrResult = pcall(func)
621 -- A Lua error was thrown
622 return false, "Failed: " .. errOrResult
625 -- errOrResult will be the value
626 return true, errOrResult
629 minetest.register_on_player_receive_fields(function(player, formname, fields)
630 if not (player and player:is_player()) then
633 if formname == "testtools:entity_list" then
634 local name = player:get_player_name()
635 if fields.entity_list then
636 local expl = minetest.explode_textlist_event(fields.entity_list)
637 if expl.type == "DCL" then
638 local pos = vector.add(player:get_pos(), {x=0,y=1,z=0})
639 selections[name] = expl.index
640 minetest.add_entity(pos, get_entity_list()[expl.index])
642 elseif expl.type == "CHG" then
643 selections[name] = expl.index
646 elseif fields.spawn and selections[name] then
647 local pos = vector.add(player:get_pos(), {x=0,y=1,z=0})
648 minetest.add_entity(pos, get_entity_list()[selections[name]])
651 elseif formname == "testtools:object_editor" then
652 local name = player:get_player_name()
653 if fields.object_props then
654 local expl = minetest.explode_textlist_event(fields.object_props)
655 if expl.type == "DCL" or expl.type == "CHG" then
656 property_formspec_index[name] = expl.index
658 local props = selected_objects[name]:get_properties()
659 local keys = property_formspec_data[name]
660 if (not property_formspec_index[name]) or (not props) then
663 local key = keys[property_formspec_index[name]]
664 editor_formspec_selindex[name] = expl.index
665 editor_formspec(name, selected_objects[name], prop_to_string(props[key]), expl.index)
669 if fields.key_enter_field == "value" or fields.submit then
670 local props = selected_objects[name]:get_properties()
671 local keys = property_formspec_data[name]
672 if (not property_formspec_index[name]) or (not props) then
675 local key = keys[property_formspec_index[name]]
679 local success, str = use_loadstring(fields.value, player)
683 minetest.chat_send_player(name, str)
686 selected_objects[name]:set_properties(props)
687 local sel = editor_formspec_selindex[name]
688 editor_formspec(name, selected_objects[name], prop_to_string(props[key]), sel)