]> git.lizzy.rs Git - testtools.git/commitdiff
Initial Commit master
authorElias Fleckenstein <eliasfleckenstein@web.de>
Thu, 3 Sep 2020 11:59:39 +0000 (13:59 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Thu, 3 Sep 2020 11:59:39 +0000 (13:59 +0200)
13 files changed:
README.md [new file with mode: 0644]
init.lua [new file with mode: 0644]
mod.conf [new file with mode: 0644]
textures/testtools_entity_rotator.png [new file with mode: 0644]
textures/testtools_entity_scaler.png [new file with mode: 0644]
textures/testtools_entity_spawner.png [new file with mode: 0644]
textures/testtools_falling_node_tool.png [new file with mode: 0644]
textures/testtools_node_setter.png [new file with mode: 0644]
textures/testtools_object_attacher.png [new file with mode: 0644]
textures/testtools_object_editor.png [new file with mode: 0644]
textures/testtools_object_mover.png [new file with mode: 0644]
textures/testtools_param2tool.png [new file with mode: 0644]
textures/testtools_remover.png [new file with mode: 0644]

diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..9cfe29e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,99 @@
+# Test Tools readme
+
+Test Tools is a mod for developers that adds a bunch of tools to directly manipulate nodes and entities. This is great for quickly testing out stuff.
+
+Here's the list of tools:
+
+## Remover
+Removes nodes and non-player entities that you punch.
+
+## Node Setter
+Replace a node with another one.
+
+First, punch a node you want to remember.
+Then rightclick any other node to replace it with the node you remembered.
+
+If you rightclick while pointing nothing, you can manually enter the node and param2.
+
+## Param2 Tool
+Change the value param2 of nodes.
+
+* Punch: Add 1 to param2
+* Sneak+Punch: Add 8 to param2
+* Place: Subtract 1 from param2
+* Sneak+Place: Subtract 8 from param2
+
+Note: Use the debug screen (F5) to see the param2 of the pointed node.
+
+## Falling Node Tool
+Turns nodes into falling nodes.
+
+Usage:
+
+* Punch node: Make it fall
+* Place: Try to teleport up to 2 units upwards, then make it fall
+
+## Entity Rotator
+Changes the entity rotation (with `set_rotation`).
+
+Usage:
+
+* Punch entity: Rotate yaw
+* Punch entity while holding down “Sneak” key: Rotate pitch
+* Punch entity while holding down “Special” key (aka “Aux”): Rotate roll
+
+Each usage rotates the entity by 22.5°.
+
+## Entity Spawner
+Spawns entities.
+
+Usage:
+
+* Punch to select entity or spawn one directly
+* Place to place selected entity
+
+## Object Property Editor
+Edits properties of objects.
+
+Usage:
+
+* Punch object to open a formspec that allows you to view and edit properties
+* Punch air to edit properties of your own player object
+
+To edit a property, select it in the list, enter a new value (in Lua syntax)
+and hit “Submit”.
+
+## Object Attacher
+Allows you to attach an object to another one.
+
+Basic usage:
+* First select the parent object, then the child object that should be attached
+* Selecting an object is done by punching it
+* Sneak+punch to detach selected object
+* If you punch air, you select yourself
+
+Configuration:
+* Place: Increase attachment Y position
+* Sneak+place: decrease attachment Y position
+* Aux+place: Increase attachment X rotation
+* Aux+Sneak+Rightclick: Decrease attachment X rotation
+
+Hint: To detach all objects nearby you (including on yourself), use the
+`/detach` server command.
+
+## Object Mover
+Move an object by a given distance.
+
+Usage:
+* Punch object into the direction you want to move it
+* Sneak+punch: Move object towards you
+* Place: Increase move distance
+* Sneak+place: Decrease move distance
+
+## Entity Visual Scaler
+Change visual size of entities
+
+Usage:
+
+* Punch entity to increase visual size
+* Sneak+punch entity to decrease visual size
diff --git a/init.lua b/init.lua
new file mode 100644 (file)
index 0000000..d68a086
--- /dev/null
+++ b/init.lua
@@ -0,0 +1,692 @@
+local S = minetest.get_translator("testtools")
+local F = minetest.formspec_escape
+
+-- TODO: Add a Node Metadata tool
+
+-- Param 2 Tool: Set param2 value of tools
+-- Punch: +1
+-- Punch+Shift:        +8
+-- Place: -1
+-- Place+Shift:        -8
+minetest.register_tool("testtools:param2tool", {
+       description = S("Param2 Tool"),
+       inventory_image = "testtools_param2tool.png",
+       groups = { testtool = 1, disable_repair = 1 },
+       on_use = function(itemstack, user, pointed_thing)
+               local pos = minetest.get_pointed_thing_position(pointed_thing)
+               if pointed_thing.type ~= "node" or (not pos) then
+                       return
+               end
+               local add = 1
+               if user then
+                       local ctrl = user:get_player_control()
+                       if ctrl.sneak then
+                               add = 8
+                       end
+               end
+               local node = minetest.get_node(pos)
+               node.param2 = node.param2 + add
+               minetest.swap_node(pos, node)
+       end,
+       on_place = function(itemstack, user, pointed_thing)
+               local pos = minetest.get_pointed_thing_position(pointed_thing)
+               if pointed_thing.type ~= "node" or (not pos) then
+                       return
+               end
+               local add = -1
+               if user then
+                       local ctrl = user:get_player_control()
+                       if ctrl.sneak then
+                               add = -8
+                       end
+               end
+               local node = minetest.get_node(pos)
+               node.param2 = node.param2 + add
+               minetest.swap_node(pos, node)
+       end,
+})
+
+minetest.register_tool("testtools:node_setter", {
+       description = S("Node Setter"),
+       inventory_image = "testtools_node_setter.png",
+       groups = { testtool = 1, disable_repair = 1 },
+       on_use = function(itemstack, user, pointed_thing)
+               local pos = minetest.get_pointed_thing_position(pointed_thing)
+               if pointed_thing.type == "nothing" then
+                       local meta = itemstack:get_meta()
+                       meta:set_string("node", "air")
+                       meta:set_int("node_param2", 0)
+                       if user and user:is_player() then
+                               minetest.chat_send_player(user:get_player_name(), S("Now placing: @1 (param2=@2)", "air", 0))
+                       end
+                       return itemstack
+               elseif pointed_thing.type ~= "node" or (not pos) then
+                       return
+               end
+               local node = minetest.get_node(pos)
+               local meta = itemstack:get_meta()
+               meta:set_string("node", node.name)
+               meta:set_int("node_param2", node.param2)
+               if user and user:is_player() then
+                       minetest.chat_send_player(user:get_player_name(), S("Now placing: @1 (param2=@2)", node.name, node.param2))
+               end
+               return itemstack
+       end,
+       on_secondary_use = function(itemstack, user, pointed_thing)
+               local meta = itemstack:get_meta()
+               local nodename = meta:get_string("node") or ""
+               local param2 = meta:get_int("node_param2") or 0
+
+               minetest.show_formspec(user:get_player_name(), "testtools:node_setter",
+                       "size[4,4]"..
+                       "field[0.5,1;3,1;nodename;"..F(S("Node name (itemstring):"))..";"..F(nodename).."]"..
+                       "field[0.5,2;3,1;param2;"..F(S("param2:"))..";"..F(tostring(param2)).."]"..
+                       "button_exit[0.5,3;3,1;submit;"..F(S("Submit")).."]"
+               )
+       end,
+       on_place = function(itemstack, user, pointed_thing)
+               local pos = minetest.get_pointed_thing_position(pointed_thing)
+               local meta = itemstack:get_meta()
+               local nodename = meta:get_string("node")
+               if nodename == "" and user and user:is_player() then
+                       minetest.chat_send_player(user:get_player_name(), S("Punch a node first!"))
+                       return
+               end
+               local param2 = meta:get_int("node_param2")
+               if not param2 then
+                       param2 = 0
+               end
+               local node = { name = nodename, param2 = param2 }
+               if not minetest.registered_nodes[nodename] then
+                       minetest.chat_send_player(user:get_player_name(), S("Cannot set unknown node: @1", nodename))
+                       return
+               end
+               minetest.set_node(pos, node)
+       end,
+})
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+       if formname == "testtools:node_setter" then
+               local playername = player:get_player_name()
+               local witem = player:get_wielded_item()
+               if witem:get_name() == "testtools:node_setter" then
+                       if fields.nodename and fields.param2 then
+                               local param2 = tonumber(fields.param2)
+                               if not param2 then
+                                       return
+                               end
+                               local meta = witem:get_meta()
+                               meta:set_string("node", fields.nodename)
+                               meta:set_int("node_param2", param2)
+                               player:set_wielded_item(witem)
+                       end
+               end
+       end
+end)
+
+minetest.register_tool("testtools:remover", {
+       description = S("Remover"),
+       inventory_image = "testtools_remover.png",
+       groups = { testtool = 1, disable_repair = 1 },
+       on_use = function(itemstack, user, pointed_thing)
+               local pos = minetest.get_pointed_thing_position(pointed_thing)
+               if pointed_thing.type == "node" and pos ~= nil then
+                       minetest.remove_node(pos)
+               elseif pointed_thing.type == "object" then
+                       local obj = pointed_thing.ref
+                       if not obj:is_player() then
+                               obj:remove()
+                       end
+               end
+       end,
+})
+
+minetest.register_tool("testtools:falling_node_tool", {
+       description = S("Falling Node Tool"),
+       inventory_image = "testtools_falling_node_tool.png",
+       groups = { testtool = 1, disable_repair = 1 },
+       on_place = function(itemstack, user, pointed_thing)
+               -- Teleport node 1-2 units upwards (if possible) and make it fall
+               local pos = minetest.get_pointed_thing_position(pointed_thing)
+               if pointed_thing.type ~= "node" or (not pos) then
+                       return
+               end
+               local ok = false
+               local highest
+               for i=1,2 do
+                       local above = {x=pos.x,y=pos.y+i,z=pos.z}
+                       local n2 = minetest.get_node(above)
+                       local def2 = minetest.registered_nodes[n2.name]
+                       if def2 and (not def2.walkable) then
+                               highest = above
+                       else
+                               break
+                       end
+               end
+               if highest then
+                       local node = minetest.get_node(pos)
+                       local metatable = minetest.get_meta(pos):to_table()
+                       minetest.remove_node(pos)
+                       minetest.set_node(highest, node)
+                       local meta_highest = minetest.get_meta(highest)
+                       meta_highest:from_table(metatable)
+                       ok = minetest.spawn_falling_node(highest)
+               else
+                       ok = minetest.spawn_falling_node(pos)
+               end
+               if not ok and user and user:is_player() then
+                       minetest.chat_send_player(user:get_player_name(), S("Falling node could not be spawned!"))
+               end
+       end,
+       on_use = function(itemstack, user, pointed_thing)
+               local pos = minetest.get_pointed_thing_position(pointed_thing)
+               if pointed_thing.type ~= "node" or (not pos) then
+                       return
+               end
+               local ok = minetest.spawn_falling_node(pos)
+               if not ok and user and user:is_player() then
+                       minetest.chat_send_player(user:get_player_name(), S("Falling node could not be spawned!"))
+               end
+       end,
+})
+
+minetest.register_tool("testtools:rotator", {
+       description = S("Entity Rotator"),
+       inventory_image = "testtools_entity_rotator.png",
+       groups = { testtool = 1, disable_repair = 1 },
+       on_use = function(itemstack, user, pointed_thing)
+               if pointed_thing.type ~= "object" then
+                       return
+               end
+               local obj = pointed_thing.ref
+               if obj:is_player() then
+                       -- No player rotation
+                       return
+               else
+                       local axis = "y"
+                       if user and user:is_player() then
+                               local ctrl = user:get_player_control()
+                               if ctrl.sneak then
+                                       axis = "x"
+                               elseif ctrl.aux1 then
+                                       axis = "z"
+                               end
+                       end
+                       local rot = obj:get_rotation()
+                       rot[axis] = rot[axis] + math.pi/8
+                       if rot[axis] > math.pi*2 then
+                               rot[axis] = rot[axis] - math.pi*2
+                       end
+                       obj:set_rotation(rot)
+               end
+       end,
+})
+
+local mover_config = function(itemstack, user, pointed_thing)
+       if not (user and user:is_player()) then
+               return
+       end
+       local name = user:get_player_name()
+       local ctrl = user:get_player_control()
+       local meta = itemstack:get_meta()
+       local dist = 1.0
+       if meta:contains("distance") then
+               dist = meta:get_int("distance")
+       end
+       if ctrl.sneak then
+               dist = dist - 1
+       else
+               dist = dist + 1
+       end
+       meta:set_int("distance", dist)
+       minetest.chat_send_player(user:get_player_name(), S("distance=@1/10", dist*2))
+       return itemstack
+end
+
+minetest.register_tool("testtools:object_mover", {
+       description = S("Object Mover"),
+       inventory_image = "testtools_object_mover.png",
+       groups = { testtool = 1, disable_repair = 1 },
+       on_place = mover_config,
+       on_secondary_use = mover_config,
+       on_use = function(itemstack, user, pointed_thing)
+               if pointed_thing.type ~= "object" then
+                       return
+               end
+               local obj = pointed_thing.ref
+               if not (user and user:is_player()) then
+                       return
+               end
+               local yaw = user:get_look_horizontal()
+               local dir = minetest.yaw_to_dir(yaw)
+               local pos = obj:get_pos()
+               local pitch = user:get_look_vertical()
+               if pitch > 0.25 * math.pi then
+                       dir.y = -1
+                       dir.x = 0
+                       dir.z = 0
+               elseif pitch < -0.25 * math.pi then
+                       dir.y = 1
+                       dir.x = 0
+                       dir.z = 0
+               end
+               local ctrl = user:get_player_control()
+               if ctrl.sneak then
+                       dir = vector.multiply(dir, -1)
+               end
+               local meta = itemstack:get_meta()
+               if meta:contains("distance") then
+                       local dist = meta:get_int("distance")
+                       dir = vector.multiply(dir, dist*0.2)
+               end
+               pos = vector.add(pos, dir)
+               obj:set_pos(pos)
+       end,
+})
+
+
+
+minetest.register_tool("testtools:entity_scaler", {
+       description = S("Entity Visual Scaler"),
+       inventory_image = "testtools_entity_scaler.png",
+       groups = { testtool = 1, disable_repair = 1 },
+       on_use = function(itemstack, user, pointed_thing)
+               if pointed_thing.type ~= "object" then
+                       return
+               end
+               local obj = pointed_thing.ref
+               if obj:is_player() then
+                       -- No player scaling
+                       return
+               else
+                       local diff = 0.1
+                       if user and user:is_player() then
+                               local ctrl = user:get_player_control()
+                               if ctrl.sneak then
+                                       diff = -0.1
+                               end
+                       end
+                       local prop = obj:get_properties()
+                       if not prop.visual_size then
+                               prop.visual_size = { x=1, y=1, z=1 }
+                       else
+                               prop.visual_size = { x=prop.visual_size.x+diff, y=prop.visual_size.y+diff, z=prop.visual_size.z+diff }
+                               if prop.visual_size.x <= 0.1 then
+                                       prop.visual_size.x = 0.1
+                               end
+                               if prop.visual_size.y <= 0.1 then
+                                       prop.visual_size.y = 0.1
+                               end
+                               if prop.visual_size.z <= 0.1 then
+                                       prop.visual_size.z = 0.1
+                               end
+                       end
+                       obj:set_properties(prop)
+               end
+       end,
+})
+
+local selections = {}
+local entity_list
+local function get_entity_list()
+       if entity_list then
+               return entity_list
+       end
+       local ents = minetest.registered_entities
+       local list = {}
+       for k,_ in pairs(ents) do
+               table.insert(list, k)
+       end
+       table.sort(list)
+       entity_list = list
+       return entity_list
+end
+minetest.register_tool("testtools:entity_spawner", {
+       description = S("Entity Spawner"),
+       inventory_image = "testtools_entity_spawner.png",
+       groups = { testtool = 1, disable_repair = 1 },
+       on_place = function(itemstack, user, pointed_thing)
+               local name = user:get_player_name()
+               if selections[name] and pointed_thing.type == "node" then
+                       local pos = pointed_thing.above
+                       minetest.add_entity(pos, get_entity_list()[selections[name]])
+               end
+       end,
+       on_use = function(itemstack, user, pointed_thing)
+               if pointed_thing.type == "object" then
+                       return
+               end
+               if user and user:is_player() then
+                       local list = table.concat(get_entity_list(), ",")
+                       local name = user:get_player_name()
+                       local sel = selections[name] or ""
+                       minetest.show_formspec(name, "testtools:entity_list",
+                               "size[9,9]"..
+                               "textlist[0,0;9,8;entity_list;"..list..";"..sel..";false]"..
+                               "button[0,8;4,1;spawn;Spawn entity]"
+                       )
+               end
+       end,
+})
+
+local function prop_to_string(property)
+       if type(property) == "string" then
+               return "\"" .. property .. "\""
+       elseif type(property) == "table" then
+               return tostring(dump(property)):gsub("\n", "")
+       else
+               return tostring(property)
+       end
+end
+
+local property_formspec_data = {}
+local property_formspec_index = {}
+local selected_objects = {}
+local function get_object_properties_form(obj, playername)
+       if not playername then return "" end
+       local props = obj:get_properties()
+       local str = ""
+       property_formspec_data[playername] = {}
+       local proplist = {}
+       for k,_ in pairs(props) do
+               table.insert(proplist, k)
+       end
+       table.sort(proplist)
+       for p=1, #proplist do
+               local k = proplist[p]
+               local v = props[k]
+               local newline = ""
+               newline = k .. " = "
+               newline = newline .. prop_to_string(v)
+               str = str .. F(newline)
+               if p < #proplist then
+                       str = str .. ","
+               end
+               table.insert(property_formspec_data[playername], k)
+       end
+       return str
+end
+
+local editor_formspec_selindex = {}
+
+local editor_formspec = function(playername, obj, value, sel)
+       if not value then
+               value = ""
+       end
+       if not sel then
+               sel = ""
+       end
+       local list = get_object_properties_form(obj, playername)
+       local title
+       if obj:is_player() then
+               title = S("Object properties of player “@1”", obj:get_player_name())
+       else
+               local ent = obj:get_luaentity()
+               title = S("Object properties of @1", ent.name)
+       end
+       minetest.show_formspec(playername, "testtools:object_editor",
+               "size[9,9]"..
+               "label[0,0;"..F(title).."]"..
+               "textlist[0,0.5;9,7.5;object_props;"..list..";"..sel..";false]"..
+               "field[0.2,8.75;8,1;value;"..F(S("Value"))..";"..F(value).."]"..
+               "field_close_on_enter[value;false]"..
+               "button[8,8.5;1,1;submit;"..F(S("Submit")).."]"
+       )
+end
+
+minetest.register_tool("testtools:object_editor", {
+       description = S("Object Property Editor"),
+       inventory_image = "testtools_object_editor.png",
+       groups = { testtool = 1, disable_repair = 1 },
+       on_use = function(itemstack, user, pointed_thing)
+               if user and user:is_player() then
+                       local name = user:get_player_name()
+
+                       if pointed_thing.type == "object" then
+                               selected_objects[name] = pointed_thing.ref
+                       elseif pointed_thing.type == "nothing" then
+                               -- Use on yourself if pointing nothing
+                               selected_objects[name] = user
+                       else
+                               -- Unsupported pointed thing
+                               return
+                       end
+
+                       local sel = editor_formspec_selindex[name]
+                       local val
+                       if selected_objects[name] and selected_objects[name]:get_properties() then
+                               local props = selected_objects[name]:get_properties()
+                               local keys = property_formspec_data[name]
+                               if property_formspec_index[name] and props then
+                                       local key = keys[property_formspec_index[name]]
+                                       val = prop_to_string(props[key])
+                               end
+                       end
+
+                       editor_formspec(name, selected_objects[name], val, sel)
+               end
+       end,
+})
+
+local ent_parent = {}
+local ent_child = {}
+local DEFAULT_ATTACH_OFFSET_Y = 11
+
+local attacher_config = function(itemstack, user, pointed_thing)
+       if not (user and user:is_player()) then
+               return
+       end
+       if pointed_thing.type == "object" then
+               return
+       end
+       local name = user:get_player_name()
+       local ctrl = user:get_player_control()
+       local meta = itemstack:get_meta()
+       if ctrl.aux1 then
+               local rot_x = meta:get_float("rot_x")
+               if ctrl.sneak then
+                       rot_x = rot_x - math.pi/8
+               else
+                       rot_x = rot_x + math.pi/8
+               end
+               if rot_x > 6.2 then
+                       rot_x = 0
+               elseif rot_x < 0 then
+                       rot_x = math.pi * (15/8)
+               end
+               minetest.chat_send_player(name, S("rotation=@1", minetest.pos_to_string({x=rot_x,y=0,z=0})))
+               meta:set_float("rot_x", rot_x)
+       else
+               local pos_y
+               if meta:contains("pos_y") then
+                       pos_y = meta:get_int("pos_y")
+               else
+                       pos_y = DEFAULT_ATTACH_OFFSET_Y
+               end
+               if ctrl.sneak then
+                       pos_y = pos_y - 1
+               else
+                       pos_y = pos_y + 1
+               end
+               minetest.chat_send_player(name, S("position=@1", minetest.pos_to_string({x=0,y=pos_y,z=0})))
+               meta:set_int("pos_y", pos_y)
+       end
+       return itemstack
+end
+
+minetest.register_tool("testtools:object_attacher", {
+       description = S("Object Attacher"),
+       inventory_image = "testtools_object_attacher.png",
+       groups = { testtool = 1, disable_repair = 1 },
+       on_place = attacher_config,
+       on_secondary_use = attacher_config,
+       on_use = function(itemstack, user, pointed_thing)
+               if user and user:is_player() then
+                       local name = user:get_player_name()
+                       local selected_object
+                       if pointed_thing.type == "object" then
+                               selected_object = pointed_thing.ref
+                       elseif pointed_thing.type == "nothing" then
+                               selected_object = user
+                       else
+                               return
+                       end
+                       local ctrl = user:get_player_control()
+                       if ctrl.sneak then
+                               if selected_object:get_attach() then
+                                       selected_object:set_detach()
+                                       minetest.chat_send_player(name, S("Object detached!"))
+                               else
+                                       minetest.chat_send_player(name, S("Object is not attached!"))
+                               end
+                               return
+                       end
+                       local parent = ent_parent[name]
+                       local child = ent_child[name]
+                       local ename = S("<unknown>")
+                       if not parent then
+                               parent = selected_object
+                               ent_parent[name] = parent
+                       elseif not child then
+                               child = selected_object
+                               ent_child[name] = child
+                       end
+                       local entity = selected_object:get_luaentity()
+                       if entity then
+                               ename = entity.name
+                       elseif selected_object:is_player() then
+                               ename = selected_object:get_player_name()
+                       end
+                       if selected_object == parent then
+                               minetest.chat_send_player(name, S("Parent object selected: @1", ename))
+                       elseif selected_object == child then
+                               minetest.chat_send_player(name, S("Child object selected: @1", ename))
+                       end
+                       if parent and child then
+                               if parent == child then
+                                       minetest.chat_send_player(name, S("Can't attach an object to itself!"))
+                                       ent_parent[name] = nil
+                                       ent_child[name] = nil
+                                       return
+                               end
+                               local meta = itemstack:get_meta()
+                               local y
+                               if meta:contains("pos_y") then
+                                       y = meta:get_int("pos_y")
+                               else
+                                       y = DEFAULT_ATTACH_OFFSET_Y
+                               end
+                               local rx = meta:get_float("rot_x") or 0
+                               local offset = {x=0,y=y,z=0}
+                               local angle = {x=rx,y=0,z=0}
+                               child:set_attach(parent, "", offset, angle)
+                               local check_parent = child:get_attach()
+                               if check_parent then
+                                       minetest.chat_send_player(name, S("Object attached! position=@1, rotation=@2",
+                                               minetest.pos_to_string(offset), minetest.pos_to_string(angle)))
+                               else
+                                       minetest.chat_send_player(name, S("Attachment failed!"))
+                               end
+                               ent_parent[name] = nil
+                               ent_child[name] = nil
+                       end
+               end
+       end,
+})
+
+-- Use loadstring to parse param as a Lua value
+local function use_loadstring(param, player)
+       -- For security reasons, require 'server' priv, just in case
+       -- someone is actually crazy enough to run this on a public server.
+       local privs = minetest.get_player_privs(player:get_player_name())
+       if not privs.server then
+               return false, "You need 'server' privilege to change object properties!"
+       end
+       if not param then
+               return false, "Failed: parameter is nil"
+       end
+       --[[ DANGER ZONE ]]
+       -- Interpret string as Lua value
+       local func, errormsg = loadstring("return (" .. param .. ")")
+       if not func then
+               return false, "Failed: " .. errormsg
+       end
+
+       -- Apply sandbox here using setfenv
+       setfenv(func, {})
+
+       -- Run it
+       local good, errOrResult = pcall(func)
+       if not good then
+               -- A Lua error was thrown
+               return false, "Failed: " .. errOrResult
+       end
+
+       -- errOrResult will be the value
+       return true, errOrResult
+end
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+       if not (player and player:is_player()) then
+               return
+       end
+       if formname == "testtools:entity_list" then
+               local name = player:get_player_name()
+               if fields.entity_list then
+                       local expl = minetest.explode_textlist_event(fields.entity_list)
+                       if expl.type == "DCL" then
+                               local pos = vector.add(player:get_pos(), {x=0,y=1,z=0})
+                               selections[name] = expl.index
+                               minetest.add_entity(pos, get_entity_list()[expl.index])
+                               return
+                       elseif expl.type == "CHG" then
+                               selections[name] = expl.index
+                               return
+                       end
+               elseif fields.spawn and selections[name] then
+                       local pos = vector.add(player:get_pos(), {x=0,y=1,z=0})
+                       minetest.add_entity(pos, get_entity_list()[selections[name]])
+                       return
+               end
+       elseif formname == "testtools:object_editor" then
+               local name = player:get_player_name()
+               if fields.object_props then
+                       local expl = minetest.explode_textlist_event(fields.object_props)
+                       if expl.type == "DCL" or expl.type == "CHG" then
+                               property_formspec_index[name] = expl.index
+
+                               local props = selected_objects[name]:get_properties()
+                               local keys = property_formspec_data[name]
+                               if (not property_formspec_index[name]) or (not props) then
+                                       return
+                               end
+                               local key = keys[property_formspec_index[name]]
+                               editor_formspec_selindex[name] = expl.index
+                               editor_formspec(name, selected_objects[name], prop_to_string(props[key]), expl.index)
+                               return
+                       end
+               end
+               if fields.key_enter_field == "value" or fields.submit then
+                       local props = selected_objects[name]:get_properties()
+                       local keys = property_formspec_data[name]
+                       if (not property_formspec_index[name]) or (not props) then
+                               return
+                       end
+                       local key = keys[property_formspec_index[name]]
+                       if not key then
+                               return
+                       end
+                       local success, str = use_loadstring(fields.value, player)
+                       if success then
+                               props[key] = str
+                       else
+                               minetest.chat_send_player(name, str)
+                               return
+                       end
+                       selected_objects[name]:set_properties(props)
+                       local sel = editor_formspec_selindex[name]
+                       editor_formspec(name, selected_objects[name], prop_to_string(props[key]), sel)
+                       return
+               end
+       end
+end)
diff --git a/mod.conf b/mod.conf
new file mode 100644 (file)
index 0000000..cde1b26
--- /dev/null
+++ b/mod.conf
@@ -0,0 +1,2 @@
+name = testtools
+description = Some tools to directly manipulate nodes and entities. Great for development and testing
diff --git a/textures/testtools_entity_rotator.png b/textures/testtools_entity_rotator.png
new file mode 100644 (file)
index 0000000..17ebb2d
Binary files /dev/null and b/textures/testtools_entity_rotator.png differ
diff --git a/textures/testtools_entity_scaler.png b/textures/testtools_entity_scaler.png
new file mode 100644 (file)
index 0000000..4909c25
Binary files /dev/null and b/textures/testtools_entity_scaler.png differ
diff --git a/textures/testtools_entity_spawner.png b/textures/testtools_entity_spawner.png
new file mode 100644 (file)
index 0000000..6199e01
Binary files /dev/null and b/textures/testtools_entity_spawner.png differ
diff --git a/textures/testtools_falling_node_tool.png b/textures/testtools_falling_node_tool.png
new file mode 100644 (file)
index 0000000..30099a7
Binary files /dev/null and b/textures/testtools_falling_node_tool.png differ
diff --git a/textures/testtools_node_setter.png b/textures/testtools_node_setter.png
new file mode 100644 (file)
index 0000000..8599438
Binary files /dev/null and b/textures/testtools_node_setter.png differ
diff --git a/textures/testtools_object_attacher.png b/textures/testtools_object_attacher.png
new file mode 100644 (file)
index 0000000..4d9bf6f
Binary files /dev/null and b/textures/testtools_object_attacher.png differ
diff --git a/textures/testtools_object_editor.png b/textures/testtools_object_editor.png
new file mode 100644 (file)
index 0000000..d1ce9ce
Binary files /dev/null and b/textures/testtools_object_editor.png differ
diff --git a/textures/testtools_object_mover.png b/textures/testtools_object_mover.png
new file mode 100644 (file)
index 0000000..8b14e9f
Binary files /dev/null and b/textures/testtools_object_mover.png differ
diff --git a/textures/testtools_param2tool.png b/textures/testtools_param2tool.png
new file mode 100644 (file)
index 0000000..dbc6635
Binary files /dev/null and b/textures/testtools_param2tool.png differ
diff --git a/textures/testtools_remover.png b/textures/testtools_remover.png
new file mode 100644 (file)
index 0000000..73f14cd
Binary files /dev/null and b/textures/testtools_remover.png differ