mirror of
https://github.com/minetest/minetest.git
synced 2024-12-23 14:42:24 +01:00
1068 lines
31 KiB
Lua
1068 lines
31 KiB
Lua
local S = core.get_translator("testtools")
|
|
local F = core.formspec_escape
|
|
|
|
testtools = {}
|
|
|
|
dofile(core.get_modpath("testtools") .. "/light.lua")
|
|
dofile(core.get_modpath("testtools") .. "/privatizer.lua")
|
|
dofile(core.get_modpath("testtools") .. "/particles.lua")
|
|
dofile(core.get_modpath("testtools") .. "/node_box_visualizer.lua")
|
|
|
|
local pointabilities_nodes = {
|
|
nodes = {
|
|
["group:blocking_pointable_test"] = true,
|
|
["group:not_pointable_test"] = true,
|
|
},
|
|
}
|
|
|
|
local pointabilities_objects = {
|
|
objects = {
|
|
["group:blocking_pointable_test"] = true,
|
|
["group:not_pointable_test"] = true,
|
|
},
|
|
}
|
|
|
|
core.register_tool("testtools:param2tool", {
|
|
description = S("Param2 Tool") .."\n"..
|
|
S("Modify param2 value of nodes") .."\n"..
|
|
S("Punch: +1") .."\n"..
|
|
S("Sneak+Punch: +8") .."\n"..
|
|
S("Place: -1") .."\n"..
|
|
S("Sneak+Place: -8"),
|
|
inventory_image = "testtools_param2tool.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = pointabilities_nodes,
|
|
on_use = function(itemstack, user, pointed_thing)
|
|
local pos = core.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 = core.get_node(pos)
|
|
node.param2 = node.param2 + add
|
|
core.swap_node(pos, node)
|
|
end,
|
|
on_place = function(itemstack, user, pointed_thing)
|
|
local pos = core.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 = core.get_node(pos)
|
|
node.param2 = node.param2 + add
|
|
core.swap_node(pos, node)
|
|
end,
|
|
})
|
|
|
|
core.register_tool("testtools:node_setter", {
|
|
description = S("Node Setter") .."\n"..
|
|
S("Replace pointed node with something else") .."\n"..
|
|
S("Punch: Select pointed node") .."\n"..
|
|
S("Place on node: Replace node with selected node") .."\n"..
|
|
S("Place in air: Manually select a node"),
|
|
inventory_image = "testtools_node_setter.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = pointabilities_nodes,
|
|
on_use = function(itemstack, user, pointed_thing)
|
|
local pos = core.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
|
|
core.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 = core.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
|
|
core.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
|
|
|
|
core.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 = core.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
|
|
core.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 core.registered_nodes[nodename] then
|
|
core.chat_send_player(user:get_player_name(), S("Cannot set unknown node: @1", nodename))
|
|
return
|
|
end
|
|
core.set_node(pos, node)
|
|
end,
|
|
})
|
|
|
|
core.register_tool("testtools:remover", {
|
|
description = S("Remover") .."\n"..
|
|
S("Punch: Remove pointed node or object"),
|
|
inventory_image = "testtools_remover.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = {
|
|
nodes = pointabilities_nodes.nodes,
|
|
objects = pointabilities_objects.objects,
|
|
},
|
|
on_use = function(itemstack, user, pointed_thing)
|
|
local pos = core.get_pointed_thing_position(pointed_thing)
|
|
if pointed_thing.type == "node" and pos ~= nil then
|
|
core.remove_node(pos)
|
|
elseif pointed_thing.type == "object" then
|
|
local obj = pointed_thing.ref
|
|
if not obj:is_player() then
|
|
obj:remove()
|
|
else
|
|
core.chat_send_player(user:get_player_name(), S("Can't remove players!"))
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
|
|
core.register_tool("testtools:falling_node_tool", {
|
|
description = S("Falling Node Tool") .."\n"..
|
|
S("Punch: Make pointed node fall") .."\n"..
|
|
S("Place: Move pointed node 2 units upwards, then make it fall"),
|
|
inventory_image = "testtools_falling_node_tool.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = pointabilities_nodes,
|
|
on_place = function(itemstack, user, pointed_thing)
|
|
-- Teleport node 1-2 units upwards (if possible) and make it fall
|
|
local pos = core.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 = core.get_node(above)
|
|
local def2 = core.registered_nodes[n2.name]
|
|
if def2 and (not def2.walkable) then
|
|
highest = above
|
|
else
|
|
break
|
|
end
|
|
end
|
|
if highest then
|
|
local node = core.get_node(pos)
|
|
local metatable = core.get_meta(pos):to_table()
|
|
core.remove_node(pos)
|
|
core.set_node(highest, node)
|
|
local meta_highest = core.get_meta(highest)
|
|
meta_highest:from_table(metatable)
|
|
ok = core.spawn_falling_node(highest)
|
|
else
|
|
ok = core.spawn_falling_node(pos)
|
|
end
|
|
if not ok and user and user:is_player() then
|
|
core.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 = core.get_pointed_thing_position(pointed_thing)
|
|
if pointed_thing.type ~= "node" or (not pos) then
|
|
return
|
|
end
|
|
local ok = core.spawn_falling_node(pos)
|
|
if not ok and user and user:is_player() then
|
|
core.chat_send_player(user:get_player_name(), S("Falling node could not be spawned!"))
|
|
end
|
|
end,
|
|
})
|
|
|
|
core.register_tool("testtools:rotator", {
|
|
description = S("Entity Rotator") .. "\n" ..
|
|
S("Rotate pointed entity") .."\n"..
|
|
S("Punch: Yaw") .."\n"..
|
|
S("Sneak+Punch: Pitch") .."\n"..
|
|
S("Aux1+Punch: Roll"),
|
|
inventory_image = "testtools_entity_rotator.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = pointabilities_objects,
|
|
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)
|
|
core.chat_send_player(user:get_player_name(), S("distance=@1/10", dist*2))
|
|
return itemstack
|
|
end
|
|
|
|
core.register_tool("testtools:object_mover", {
|
|
description = S("Object Mover") .."\n"..
|
|
S("Move pointed object towards or away from you") .."\n"..
|
|
S("Punch: Move by distance").."\n"..
|
|
S("Sneak+Punch: Move by negative distance").."\n"..
|
|
S("Place: Increase distance").."\n"..
|
|
S("Sneak+Place: Decrease distance"),
|
|
inventory_image = "testtools_object_mover.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = pointabilities_objects,
|
|
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 = core.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,
|
|
})
|
|
|
|
|
|
|
|
core.register_tool("testtools:entity_scaler", {
|
|
description = S("Entity Visual Scaler") .."\n"..
|
|
S("Scale visual size of entities") .."\n"..
|
|
S("Punch: Increase size") .."\n"..
|
|
S("Sneak+Punch: Decrease scale"),
|
|
inventory_image = "testtools_entity_scaler.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = pointabilities_objects,
|
|
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,
|
|
})
|
|
|
|
|
|
-- value-weak tables, because we don't want to keep the objrefs of unloaded objects
|
|
local branded_objects = setmetatable({}, {__mode = "v"})
|
|
local next_brand_num = 1
|
|
|
|
function testtools.get_branded_object(name)
|
|
if name:sub(1, 7) == "player:" then
|
|
return core.get_player_by_name(name:sub(8))
|
|
elseif name:sub(1, 4) == "obj:" then
|
|
return branded_objects[tonumber(name:sub(5)) or 0]
|
|
end
|
|
return nil
|
|
end
|
|
|
|
core.register_tool("testtools:branding_iron", {
|
|
description = S("Branding Iron") .."\n"..
|
|
S("Give an object a temporary name.") .."\n"..
|
|
S("Punch object: Brand the object") .."\n"..
|
|
S("Punch air: Brand yourself") .."\n"..
|
|
S("The name is valid until the object unloads.") .."\n"..
|
|
S("Devices that accept the returned name also accept \"player:<playername>\" for players."),
|
|
inventory_image = "testtools_branding_iron.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = pointabilities_objects,
|
|
on_use = function(_itemstack, user, pointed_thing)
|
|
local obj
|
|
local msg
|
|
if pointed_thing.type == "object" then
|
|
obj = pointed_thing.ref
|
|
msg = "You can now refer to this object with: \"@1\""
|
|
elseif pointed_thing.type == "nothing" then
|
|
obj = user
|
|
msg = "You can now refer to yourself with: \"@1\""
|
|
else
|
|
return
|
|
end
|
|
|
|
local brand_num = next_brand_num
|
|
next_brand_num = next_brand_num + 1
|
|
branded_objects[brand_num] = obj
|
|
|
|
core.chat_send_player(user:get_player_name(), S(msg, "obj:"..brand_num))
|
|
end,
|
|
})
|
|
|
|
|
|
local selections = {}
|
|
local entity_list
|
|
local function get_entity_list()
|
|
if entity_list then
|
|
return entity_list
|
|
end
|
|
local ents = core.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
|
|
core.register_tool("testtools:entity_spawner", {
|
|
description = S("Entity Spawner") .."\n"..
|
|
S("Spawns entities") .."\n"..
|
|
S("Punch: Select entity to spawn") .."\n"..
|
|
S("Place: Spawn selected entity"),
|
|
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 pointed_thing.type == "node" then
|
|
if selections[name] then
|
|
local pos = pointed_thing.above
|
|
core.add_entity(pos, get_entity_list()[selections[name]])
|
|
else
|
|
core.chat_send_player(name, S("Select an entity first (with punch key)!"))
|
|
end
|
|
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 ""
|
|
core.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
|
|
core.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
|
|
|
|
core.register_tool("testtools:object_editor", {
|
|
description = S("Object Property Editor") .."\n"..
|
|
S("Edit properties of objects") .."\n"..
|
|
S("Punch object: Edit object") .."\n"..
|
|
S("Punch air: Edit yourself"),
|
|
inventory_image = "testtools_object_editor.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = pointabilities_objects,
|
|
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
|
|
core.chat_send_player(name, S("rotation=@1", core.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
|
|
core.chat_send_player(name, S("position=@1", core.pos_to_string({x=0,y=pos_y,z=0})))
|
|
meta:set_int("pos_y", pos_y)
|
|
end
|
|
return itemstack
|
|
end
|
|
|
|
core.register_tool("testtools:object_attacher", {
|
|
description = S("Object Attacher") .."\n"..
|
|
S("Attach object to another") .."\n"..
|
|
S("Punch objects to first select parent object, then the child object to attach") .."\n"..
|
|
S("Punch air to select yourself") .."\n"..
|
|
S("Place: Incease attachment Y offset") .."\n"..
|
|
S("Sneak+Place: Decease attachment Y offset") .."\n"..
|
|
S("Aux1+Place: Incease attachment rotation") .."\n"..
|
|
S("Aux1+Sneak+Place: Decrease attachment rotation"),
|
|
inventory_image = "testtools_object_attacher.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = pointabilities_objects,
|
|
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()
|
|
core.chat_send_player(name, S("Object detached!"))
|
|
else
|
|
core.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
|
|
core.chat_send_player(name, S("Parent object selected: @1", ename))
|
|
elseif selected_object == child then
|
|
core.chat_send_player(name, S("Child object selected: @1", ename))
|
|
end
|
|
if parent and child then
|
|
if parent == child then
|
|
core.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
|
|
core.chat_send_player(name, S("Object attached! position=@1, rotation=@2",
|
|
core.pos_to_string(offset), core.pos_to_string(angle)))
|
|
else
|
|
core.chat_send_player(name, S("Attachment failed!"))
|
|
end
|
|
ent_parent[name] = nil
|
|
ent_child[name] = nil
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
|
|
local function print_object(obj)
|
|
if obj:is_player() then
|
|
return "player '"..obj:get_player_name().."'"
|
|
elseif obj:get_luaentity() then
|
|
return "LuaEntity '"..obj:get_luaentity().name.."'"
|
|
else
|
|
return "object"
|
|
end
|
|
end
|
|
|
|
core.register_tool("testtools:children_getter", {
|
|
description = S("Children Getter") .."\n"..
|
|
S("Shows list of objects attached to object") .."\n"..
|
|
S("Punch object to show its 'children'") .."\n"..
|
|
S("Punch air to show your own 'children'"),
|
|
inventory_image = "testtools_children_getter.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
pointabilities = pointabilities_objects,
|
|
on_use = function(itemstack, user, pointed_thing)
|
|
if user and user:is_player() then
|
|
local name = user:get_player_name()
|
|
local selected_object
|
|
local self_name
|
|
if pointed_thing.type == "object" then
|
|
selected_object = pointed_thing.ref
|
|
elseif pointed_thing.type == "nothing" then
|
|
selected_object = user
|
|
else
|
|
return
|
|
end
|
|
self_name = print_object(selected_object)
|
|
local children = selected_object:get_children()
|
|
local ret = ""
|
|
for c=1, #children do
|
|
ret = ret .. "* " .. print_object(children[c])
|
|
if c < #children then
|
|
ret = ret .. "\n"
|
|
end
|
|
end
|
|
if ret == "" then
|
|
ret = S("No children attached to @1.", self_name)
|
|
else
|
|
ret = S("Children of @1:", self_name) .. "\n" .. ret
|
|
end
|
|
core.chat_send_player(user:get_player_name(), ret)
|
|
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 = core.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
|
|
|
|
-- Item Meta Editor + Node Meta Editor
|
|
local node_meta_posses = {}
|
|
local meta_latest_keylist = {}
|
|
|
|
local function show_meta_formspec(user, metatype, pos_or_item, key, value, keylist)
|
|
local textlist
|
|
if keylist then
|
|
textlist = "textlist[0,0.5;2.5,6.5;keylist;"..keylist.."]"
|
|
else
|
|
textlist = ""
|
|
end
|
|
|
|
local form = "size[15,9]"..
|
|
"label[0,0;"..F(S("Current keys:")).."]"..
|
|
textlist..
|
|
"field[3,0.5;12,1;key;"..F(S("Key"))..";"..F(key).."]"..
|
|
"textarea[3,1.5;12,6;value;"..F(S("Value (use empty value to delete key)"))..";"..F(value).."]"..
|
|
"button[4,8;3,1;set;"..F(S("Set value")).."]"
|
|
|
|
local extra_label
|
|
local formname
|
|
if metatype == "node" then
|
|
formname = "testtools:node_meta_editor"
|
|
extra_label = S("pos = @1", core.pos_to_string(pos_or_item))
|
|
else
|
|
formname = "testtools:item_meta_editor"
|
|
extra_label = S("item = @1", pos_or_item:get_name())
|
|
end
|
|
form = form .. "label[0,7.2;"..F(extra_label).."]"
|
|
|
|
core.show_formspec(user:get_player_name(), formname, form)
|
|
end
|
|
|
|
local function get_meta_keylist(meta, playername, escaped)
|
|
local keys = {}
|
|
local ekeys = {}
|
|
local mtable = meta:to_table()
|
|
for k,_ in pairs(mtable.fields) do
|
|
table.insert(keys, k)
|
|
if escaped then
|
|
table.insert(ekeys, F(k))
|
|
else
|
|
table.insert(ekeys, k)
|
|
end
|
|
end
|
|
if playername then
|
|
meta_latest_keylist[playername] = keys
|
|
end
|
|
return table.concat(ekeys, ",")
|
|
end
|
|
|
|
core.register_tool("testtools:node_meta_editor", {
|
|
description = S("Node Meta Editor") .. "\n" ..
|
|
S("Place: Edit node metadata"),
|
|
inventory_image = "testtools_node_meta_editor.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
on_place = function(itemstack, user, pointed_thing)
|
|
if pointed_thing.type ~= "node" then
|
|
return itemstack
|
|
end
|
|
if not user:is_player() then
|
|
return itemstack
|
|
end
|
|
local pos = pointed_thing.under
|
|
node_meta_posses[user:get_player_name()] = pos
|
|
local meta = core.get_meta(pos)
|
|
local inv = meta:get_inventory()
|
|
show_meta_formspec(user, "node", pos, "", "", get_meta_keylist(meta, user:get_player_name(), true))
|
|
return itemstack
|
|
end,
|
|
})
|
|
|
|
local function get_item_next_to_wielded_item(player)
|
|
local inv = player:get_inventory()
|
|
local wield = player:get_wield_index()
|
|
local itemstack = inv:get_stack("main", wield+1)
|
|
return itemstack
|
|
end
|
|
local function set_item_next_to_wielded_item(player, itemstack)
|
|
local inv = player:get_inventory()
|
|
local wield = player:get_wield_index()
|
|
inv:set_stack("main", wield+1, itemstack)
|
|
end
|
|
|
|
local function use_item_meta_editor(itemstack, user, pointed_thing)
|
|
if not user:is_player() then
|
|
return itemstack
|
|
end
|
|
local item_to_edit = get_item_next_to_wielded_item(user)
|
|
if item_to_edit:is_empty() then
|
|
core.chat_send_player(user:get_player_name(), S("Place an item next to the Item Meta Editor in your inventory first!"))
|
|
return itemstack
|
|
end
|
|
local meta = item_to_edit:get_meta()
|
|
show_meta_formspec(user, "item", item_to_edit, "", "", get_meta_keylist(meta, user:get_player_name(), true))
|
|
return itemstack
|
|
end
|
|
|
|
core.register_tool("testtools:item_meta_editor", {
|
|
description = S("Item Meta Editor") .. "\n" ..
|
|
S("Punch/Place: Edit item metadata of item in the next inventory slot"),
|
|
inventory_image = "testtools_item_meta_editor.png",
|
|
groups = { testtool = 1, disable_repair = 1 },
|
|
on_use = use_item_meta_editor,
|
|
on_secondary_use = use_item_meta_editor,
|
|
on_place = use_item_meta_editor,
|
|
})
|
|
|
|
core.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 = core.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
|
|
core.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})
|
|
core.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 = core.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
|
|
core.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
|
|
elseif 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
|
|
elseif formname == "testtools:node_meta_editor" or formname == "testtools:item_meta_editor" then
|
|
local name = player:get_player_name()
|
|
local metatype
|
|
local pos_or_item
|
|
if formname == "testtools:node_meta_editor" then
|
|
metatype = "node"
|
|
pos_or_item = node_meta_posses[name]
|
|
else
|
|
metatype = "item"
|
|
pos_or_item = get_item_next_to_wielded_item(player)
|
|
end
|
|
if fields.keylist then
|
|
local evnt = core.explode_textlist_event(fields.keylist)
|
|
if evnt.type == "DCL" or evnt.type == "CHG" then
|
|
local keylist_table = meta_latest_keylist[name]
|
|
if metatype == "node" and not pos_or_item then
|
|
return
|
|
end
|
|
local meta
|
|
if metatype == "node" then
|
|
meta = core.get_meta(pos_or_item)
|
|
else
|
|
meta = pos_or_item:get_meta()
|
|
end
|
|
if not keylist_table then
|
|
return
|
|
end
|
|
if #keylist_table == 0 then
|
|
return
|
|
end
|
|
local key = keylist_table[evnt.index]
|
|
local value = meta:get_string(key)
|
|
local keylist_escaped = {}
|
|
for k,v in pairs(keylist_table) do
|
|
keylist_escaped[k] = F(v)
|
|
end
|
|
local keylist = table.concat(keylist_escaped, ",")
|
|
show_meta_formspec(player, metatype, pos_or_item, key, value, keylist)
|
|
return
|
|
end
|
|
elseif fields.key and fields.key ~= "" and fields.value then
|
|
if metatype == "node" and not pos_or_item then
|
|
return
|
|
end
|
|
local meta
|
|
if metatype == "node" then
|
|
meta = core.get_meta(pos_or_item)
|
|
elseif metatype == "item" then
|
|
if pos_or_item:is_empty() then
|
|
return
|
|
end
|
|
meta = pos_or_item:get_meta()
|
|
end
|
|
if fields.set then
|
|
meta:set_string(fields.key, fields.value)
|
|
if metatype == "item" then
|
|
set_item_next_to_wielded_item(player, pos_or_item)
|
|
end
|
|
show_meta_formspec(player, metatype, pos_or_item, fields.key, fields.value,
|
|
get_meta_keylist(meta, name, true))
|
|
end
|
|
return
|
|
end
|
|
end
|
|
end)
|
|
|
|
core.register_on_leaveplayer(function(player)
|
|
local name = player:get_player_name()
|
|
meta_latest_keylist[name] = nil
|
|
node_meta_posses[name] = nil
|
|
end)
|
|
|
|
-- Pointing Staffs
|
|
|
|
core.register_tool("testtools:blocked_pointing_staff", {
|
|
description = S("Blocked Pointing Staff").."\n"..
|
|
S("Can point the Blocking Pointable Node/Object and "..
|
|
"the Pointable Node/Object is point blocking."),
|
|
inventory_image = "testtools_blocked_pointing_staff.png",
|
|
pointabilities = {
|
|
nodes = {
|
|
["testnodes:blocking_pointable"] = true,
|
|
["group:pointable_test"] = "blocking"
|
|
},
|
|
objects = {
|
|
["testentities:blocking_pointable"] = true,
|
|
["group:pointable_test"] = "blocking"
|
|
}
|
|
}
|
|
})
|
|
|
|
core.register_tool("testtools:ultimate_pointing_staff", {
|
|
description = S("Ultimate Pointing Staff").."\n"..
|
|
S("Can point all pointable test nodes, objects and liquids."),
|
|
inventory_image = "testtools_ultimate_pointing_staff.png",
|
|
liquids_pointable = true,
|
|
pointabilities = {
|
|
nodes = {
|
|
["group:blocking_pointable_test"] = true,
|
|
["group:pointable_test"] = true,
|
|
["testnodes:not_pointable"] = true
|
|
},
|
|
objects = {
|
|
["group:blocking_pointable_test"] = true,
|
|
["group:pointable_test"] = true,
|
|
["testentities:not_pointable"] = true
|
|
}
|
|
}
|
|
})
|