1local S = minetest.get_translator("testtools") 2local F = minetest.formspec_escape 3 4dofile(minetest.get_modpath("testtools") .. "/light.lua") 5 6-- TODO: Add a Node Metadata tool 7 8minetest.register_tool("testtools:param2tool", { 9 description = S("Param2 Tool") .."\n".. 10 S("Modify param2 value of nodes") .."\n".. 11 S("Punch: +1") .."\n".. 12 S("Sneak+Punch: +8") .."\n".. 13 S("Place: -1") .."\n".. 14 S("Sneak+Place: -8"), 15 inventory_image = "testtools_param2tool.png", 16 groups = { testtool = 1, disable_repair = 1 }, 17 on_use = function(itemstack, user, pointed_thing) 18 local pos = minetest.get_pointed_thing_position(pointed_thing) 19 if pointed_thing.type ~= "node" or (not pos) then 20 return 21 end 22 local add = 1 23 if user then 24 local ctrl = user:get_player_control() 25 if ctrl.sneak then 26 add = 8 27 end 28 end 29 local node = minetest.get_node(pos) 30 node.param2 = node.param2 + add 31 minetest.swap_node(pos, node) 32 end, 33 on_place = function(itemstack, user, pointed_thing) 34 local pos = minetest.get_pointed_thing_position(pointed_thing) 35 if pointed_thing.type ~= "node" or (not pos) then 36 return 37 end 38 local add = -1 39 if user then 40 local ctrl = user:get_player_control() 41 if ctrl.sneak then 42 add = -8 43 end 44 end 45 local node = minetest.get_node(pos) 46 node.param2 = node.param2 + add 47 minetest.swap_node(pos, node) 48 end, 49}) 50 51minetest.register_tool("testtools:node_setter", { 52 description = S("Node Setter") .."\n".. 53 S("Replace pointed node with something else") .."\n".. 54 S("Punch: Select pointed node") .."\n".. 55 S("Place on node: Replace node with selected node") .."\n".. 56 S("Place in air: Manually select a node"), 57 inventory_image = "testtools_node_setter.png", 58 groups = { testtool = 1, disable_repair = 1 }, 59 on_use = function(itemstack, user, pointed_thing) 60 local pos = minetest.get_pointed_thing_position(pointed_thing) 61 if pointed_thing.type == "nothing" then 62 local meta = itemstack:get_meta() 63 meta:set_string("node", "air") 64 meta:set_int("node_param2", 0) 65 if user and user:is_player() then 66 minetest.chat_send_player(user:get_player_name(), S("Now placing: @1 (param2=@2)", "air", 0)) 67 end 68 return itemstack 69 elseif pointed_thing.type ~= "node" or (not pos) then 70 return 71 end 72 local node = minetest.get_node(pos) 73 local meta = itemstack:get_meta() 74 meta:set_string("node", node.name) 75 meta:set_int("node_param2", node.param2) 76 if user and user:is_player() then 77 minetest.chat_send_player(user:get_player_name(), S("Now placing: @1 (param2=@2)", node.name, node.param2)) 78 end 79 return itemstack 80 end, 81 on_secondary_use = function(itemstack, user, pointed_thing) 82 local meta = itemstack:get_meta() 83 local nodename = meta:get_string("node") or "" 84 local param2 = meta:get_int("node_param2") or 0 85 86 minetest.show_formspec(user:get_player_name(), "testtools:node_setter", 87 "size[4,4]".. 88 "field[0.5,1;3,1;nodename;"..F(S("Node name (itemstring):"))..";"..F(nodename).."]".. 89 "field[0.5,2;3,1;param2;"..F(S("param2:"))..";"..F(tostring(param2)).."]".. 90 "button_exit[0.5,3;3,1;submit;"..F(S("Submit")).."]" 91 ) 92 end, 93 on_place = function(itemstack, user, pointed_thing) 94 local pos = minetest.get_pointed_thing_position(pointed_thing) 95 local meta = itemstack:get_meta() 96 local nodename = meta:get_string("node") 97 if nodename == "" and user and user:is_player() then 98 minetest.chat_send_player(user:get_player_name(), S("Punch a node first!")) 99 return 100 end 101 local param2 = meta:get_int("node_param2") 102 if not param2 then 103 param2 = 0 104 end 105 local node = { name = nodename, param2 = param2 } 106 if not minetest.registered_nodes[nodename] then 107 minetest.chat_send_player(user:get_player_name(), S("Cannot set unknown node: @1", nodename)) 108 return 109 end 110 minetest.set_node(pos, node) 111 end, 112}) 113 114minetest.register_on_player_receive_fields(function(player, formname, fields) 115 if formname == "testtools:node_setter" then 116 local playername = player:get_player_name() 117 local witem = player:get_wielded_item() 118 if witem:get_name() == "testtools:node_setter" then 119 if fields.nodename and fields.param2 then 120 local param2 = tonumber(fields.param2) 121 if not param2 then 122 return 123 end 124 local meta = witem:get_meta() 125 meta:set_string("node", fields.nodename) 126 meta:set_int("node_param2", param2) 127 player:set_wielded_item(witem) 128 end 129 end 130 end 131end) 132 133minetest.register_tool("testtools:remover", { 134 description = S("Remover") .."\n".. 135 S("Punch: Remove pointed node or object"), 136 inventory_image = "testtools_remover.png", 137 groups = { testtool = 1, disable_repair = 1 }, 138 on_use = function(itemstack, user, pointed_thing) 139 local pos = minetest.get_pointed_thing_position(pointed_thing) 140 if pointed_thing.type == "node" and pos ~= nil then 141 minetest.remove_node(pos) 142 elseif pointed_thing.type == "object" then 143 local obj = pointed_thing.ref 144 if not obj:is_player() then 145 obj:remove() 146 else 147 minetest.chat_send_player(user:get_player_name(), S("Can't remove players!")) 148 end 149 end 150 end, 151}) 152 153minetest.register_tool("testtools:falling_node_tool", { 154 description = S("Falling Node Tool") .."\n".. 155 S("Punch: Make pointed node fall") .."\n".. 156 S("Place: Move pointed node 2 units upwards, then make it fall"), 157 inventory_image = "testtools_falling_node_tool.png", 158 groups = { testtool = 1, disable_repair = 1 }, 159 on_place = function(itemstack, user, pointed_thing) 160 -- Teleport node 1-2 units upwards (if possible) and make it fall 161 local pos = minetest.get_pointed_thing_position(pointed_thing) 162 if pointed_thing.type ~= "node" or (not pos) then 163 return 164 end 165 local ok = false 166 local highest 167 for i=1,2 do 168 local above = {x=pos.x,y=pos.y+i,z=pos.z} 169 local n2 = minetest.get_node(above) 170 local def2 = minetest.registered_nodes[n2.name] 171 if def2 and (not def2.walkable) then 172 highest = above 173 else 174 break 175 end 176 end 177 if highest then 178 local node = minetest.get_node(pos) 179 local metatable = minetest.get_meta(pos):to_table() 180 minetest.remove_node(pos) 181 minetest.set_node(highest, node) 182 local meta_highest = minetest.get_meta(highest) 183 meta_highest:from_table(metatable) 184 ok = minetest.spawn_falling_node(highest) 185 else 186 ok = minetest.spawn_falling_node(pos) 187 end 188 if not ok and user and user:is_player() then 189 minetest.chat_send_player(user:get_player_name(), S("Falling node could not be spawned!")) 190 end 191 end, 192 on_use = function(itemstack, user, pointed_thing) 193 local pos = minetest.get_pointed_thing_position(pointed_thing) 194 if pointed_thing.type ~= "node" or (not pos) then 195 return 196 end 197 local ok = minetest.spawn_falling_node(pos) 198 if not ok and user and user:is_player() then 199 minetest.chat_send_player(user:get_player_name(), S("Falling node could not be spawned!")) 200 end 201 end, 202}) 203 204minetest.register_tool("testtools:rotator", { 205 description = S("Entity Rotator") .. "\n" .. 206 S("Rotate pointed entity") .."\n".. 207 S("Punch: Yaw") .."\n".. 208 S("Sneak+Punch: Pitch") .."\n".. 209 S("Aux1+Punch: Roll"), 210 inventory_image = "testtools_entity_rotator.png", 211 groups = { testtool = 1, disable_repair = 1 }, 212 on_use = function(itemstack, user, pointed_thing) 213 if pointed_thing.type ~= "object" then 214 return 215 end 216 local obj = pointed_thing.ref 217 if obj:is_player() then 218 -- No player rotation 219 return 220 else 221 local axis = "y" 222 if user and user:is_player() then 223 local ctrl = user:get_player_control() 224 if ctrl.sneak then 225 axis = "x" 226 elseif ctrl.aux1 then 227 axis = "z" 228 end 229 end 230 local rot = obj:get_rotation() 231 rot[axis] = rot[axis] + math.pi/8 232 if rot[axis] > math.pi*2 then 233 rot[axis] = rot[axis] - math.pi*2 234 end 235 obj:set_rotation(rot) 236 end 237 end, 238}) 239 240local mover_config = function(itemstack, user, pointed_thing) 241 if not (user and user:is_player()) then 242 return 243 end 244 local name = user:get_player_name() 245 local ctrl = user:get_player_control() 246 local meta = itemstack:get_meta() 247 local dist = 1.0 248 if meta:contains("distance") then 249 dist = meta:get_int("distance") 250 end 251 if ctrl.sneak then 252 dist = dist - 1 253 else 254 dist = dist + 1 255 end 256 meta:set_int("distance", dist) 257 minetest.chat_send_player(user:get_player_name(), S("distance=@1/10", dist*2)) 258 return itemstack 259end 260 261minetest.register_tool("testtools:object_mover", { 262 description = S("Object Mover") .."\n".. 263 S("Move pointed object towards or away from you") .."\n".. 264 S("Punch: Move by distance").."\n".. 265 S("Sneak+Punch: Move by negative distance").."\n".. 266 S("Place: Increase distance").."\n".. 267 S("Sneak+Place: Decrease distance"), 268 inventory_image = "testtools_object_mover.png", 269 groups = { testtool = 1, disable_repair = 1 }, 270 on_place = mover_config, 271 on_secondary_use = mover_config, 272 on_use = function(itemstack, user, pointed_thing) 273 if pointed_thing.type ~= "object" then 274 return 275 end 276 local obj = pointed_thing.ref 277 if not (user and user:is_player()) then 278 return 279 end 280 local yaw = user:get_look_horizontal() 281 local dir = minetest.yaw_to_dir(yaw) 282 local pos = obj:get_pos() 283 local pitch = user:get_look_vertical() 284 if pitch > 0.25 * math.pi then 285 dir.y = -1 286 dir.x = 0 287 dir.z = 0 288 elseif pitch < -0.25 * math.pi then 289 dir.y = 1 290 dir.x = 0 291 dir.z = 0 292 end 293 local ctrl = user:get_player_control() 294 if ctrl.sneak then 295 dir = vector.multiply(dir, -1) 296 end 297 local meta = itemstack:get_meta() 298 if meta:contains("distance") then 299 local dist = meta:get_int("distance") 300 dir = vector.multiply(dir, dist*0.2) 301 end 302 pos = vector.add(pos, dir) 303 obj:set_pos(pos) 304 end, 305}) 306 307 308 309minetest.register_tool("testtools:entity_scaler", { 310 description = S("Entity Visual Scaler") .."\n".. 311 S("Scale visual size of entities") .."\n".. 312 S("Punch: Increase size") .."\n".. 313 S("Sneak+Punch: Decrease scale"), 314 inventory_image = "testtools_entity_scaler.png", 315 groups = { testtool = 1, disable_repair = 1 }, 316 on_use = function(itemstack, user, pointed_thing) 317 if pointed_thing.type ~= "object" then 318 return 319 end 320 local obj = pointed_thing.ref 321 if obj:is_player() then 322 -- No player scaling 323 return 324 else 325 local diff = 0.1 326 if user and user:is_player() then 327 local ctrl = user:get_player_control() 328 if ctrl.sneak then 329 diff = -0.1 330 end 331 end 332 local prop = obj:get_properties() 333 if not prop.visual_size then 334 prop.visual_size = { x=1, y=1, z=1 } 335 else 336 prop.visual_size = { x=prop.visual_size.x+diff, y=prop.visual_size.y+diff, z=prop.visual_size.z+diff } 337 if prop.visual_size.x <= 0.1 then 338 prop.visual_size.x = 0.1 339 end 340 if prop.visual_size.y <= 0.1 then 341 prop.visual_size.y = 0.1 342 end 343 if prop.visual_size.z <= 0.1 then 344 prop.visual_size.z = 0.1 345 end 346 end 347 obj:set_properties(prop) 348 end 349 end, 350}) 351 352local selections = {} 353local entity_list 354local function get_entity_list() 355 if entity_list then 356 return entity_list 357 end 358 local ents = minetest.registered_entities 359 local list = {} 360 for k,_ in pairs(ents) do 361 table.insert(list, k) 362 end 363 table.sort(list) 364 entity_list = list 365 return entity_list 366end 367minetest.register_tool("testtools:entity_spawner", { 368 description = S("Entity Spawner") .."\n".. 369 S("Spawns entities") .."\n".. 370 S("Punch: Select entity to spawn") .."\n".. 371 S("Place: Spawn selected entity"), 372 inventory_image = "testtools_entity_spawner.png", 373 groups = { testtool = 1, disable_repair = 1 }, 374 on_place = function(itemstack, user, pointed_thing) 375 local name = user:get_player_name() 376 if pointed_thing.type == "node" then 377 if selections[name] then 378 local pos = pointed_thing.above 379 minetest.add_entity(pos, get_entity_list()[selections[name]]) 380 else 381 minetest.chat_send_player(name, S("Select an entity first (with punch key)!")) 382 end 383 end 384 end, 385 on_use = function(itemstack, user, pointed_thing) 386 if pointed_thing.type == "object" then 387 return 388 end 389 if user and user:is_player() then 390 local list = table.concat(get_entity_list(), ",") 391 local name = user:get_player_name() 392 local sel = selections[name] or "" 393 minetest.show_formspec(name, "testtools:entity_list", 394 "size[9,9]".. 395 "textlist[0,0;9,8;entity_list;"..list..";"..sel..";false]".. 396 "button[0,8;4,1;spawn;Spawn entity]" 397 ) 398 end 399 end, 400}) 401 402local function prop_to_string(property) 403 if type(property) == "string" then 404 return "\"" .. property .. "\"" 405 elseif type(property) == "table" then 406 return tostring(dump(property)):gsub("\n", "") 407 else 408 return tostring(property) 409 end 410end 411 412local property_formspec_data = {} 413local property_formspec_index = {} 414local selected_objects = {} 415local function get_object_properties_form(obj, playername) 416 if not playername then return "" end 417 local props = obj:get_properties() 418 local str = "" 419 property_formspec_data[playername] = {} 420 local proplist = {} 421 for k,_ in pairs(props) do 422 table.insert(proplist, k) 423 end 424 table.sort(proplist) 425 for p=1, #proplist do 426 local k = proplist[p] 427 local v = props[k] 428 local newline = "" 429 newline = k .. " = " 430 newline = newline .. prop_to_string(v) 431 str = str .. F(newline) 432 if p < #proplist then 433 str = str .. "," 434 end 435 table.insert(property_formspec_data[playername], k) 436 end 437 return str 438end 439 440local editor_formspec_selindex = {} 441 442local editor_formspec = function(playername, obj, value, sel) 443 if not value then 444 value = "" 445 end 446 if not sel then 447 sel = "" 448 end 449 local list = get_object_properties_form(obj, playername) 450 local title 451 if obj:is_player() then 452 title = S("Object properties of player “@1”", obj:get_player_name()) 453 else 454 local ent = obj:get_luaentity() 455 title = S("Object properties of @1", ent.name) 456 end 457 minetest.show_formspec(playername, "testtools:object_editor", 458 "size[9,9]".. 459 "label[0,0;"..F(title).."]".. 460 "textlist[0,0.5;9,7.5;object_props;"..list..";"..sel..";false]".. 461 "field[0.2,8.75;8,1;value;"..F(S("Value"))..";"..F(value).."]".. 462 "field_close_on_enter[value;false]".. 463 "button[8,8.5;1,1;submit;"..F(S("Submit")).."]" 464 ) 465end 466 467minetest.register_tool("testtools:object_editor", { 468 description = S("Object Property Editor") .."\n".. 469 S("Edit properties of objects") .."\n".. 470 S("Punch object: Edit object") .."\n".. 471 S("Punch air: Edit yourself"), 472 inventory_image = "testtools_object_editor.png", 473 groups = { testtool = 1, disable_repair = 1 }, 474 on_use = function(itemstack, user, pointed_thing) 475 if user and user:is_player() then 476 local name = user:get_player_name() 477 478 if pointed_thing.type == "object" then 479 selected_objects[name] = pointed_thing.ref 480 elseif pointed_thing.type == "nothing" then 481 -- Use on yourself if pointing nothing 482 selected_objects[name] = user 483 else 484 -- Unsupported pointed thing 485 return 486 end 487 488 local sel = editor_formspec_selindex[name] 489 local val 490 if selected_objects[name] and selected_objects[name]:get_properties() then 491 local props = selected_objects[name]:get_properties() 492 local keys = property_formspec_data[name] 493 if property_formspec_index[name] and props then 494 local key = keys[property_formspec_index[name]] 495 val = prop_to_string(props[key]) 496 end 497 end 498 499 editor_formspec(name, selected_objects[name], val, sel) 500 end 501 end, 502}) 503 504local ent_parent = {} 505local ent_child = {} 506local DEFAULT_ATTACH_OFFSET_Y = 11 507 508local attacher_config = function(itemstack, user, pointed_thing) 509 if not (user and user:is_player()) then 510 return 511 end 512 if pointed_thing.type == "object" then 513 return 514 end 515 local name = user:get_player_name() 516 local ctrl = user:get_player_control() 517 local meta = itemstack:get_meta() 518 if ctrl.aux1 then 519 local rot_x = meta:get_float("rot_x") 520 if ctrl.sneak then 521 rot_x = rot_x - math.pi/8 522 else 523 rot_x = rot_x + math.pi/8 524 end 525 if rot_x > 6.2 then 526 rot_x = 0 527 elseif rot_x < 0 then 528 rot_x = math.pi * (15/8) 529 end 530 minetest.chat_send_player(name, S("rotation=@1", minetest.pos_to_string({x=rot_x,y=0,z=0}))) 531 meta:set_float("rot_x", rot_x) 532 else 533 local pos_y 534 if meta:contains("pos_y") then 535 pos_y = meta:get_int("pos_y") 536 else 537 pos_y = DEFAULT_ATTACH_OFFSET_Y 538 end 539 if ctrl.sneak then 540 pos_y = pos_y - 1 541 else 542 pos_y = pos_y + 1 543 end 544 minetest.chat_send_player(name, S("position=@1", minetest.pos_to_string({x=0,y=pos_y,z=0}))) 545 meta:set_int("pos_y", pos_y) 546 end 547 return itemstack 548end 549 550minetest.register_tool("testtools:object_attacher", { 551 description = S("Object Attacher") .."\n".. 552 S("Attach object to another") .."\n".. 553 S("Punch objects to first select parent object, then the child object to attach") .."\n".. 554 S("Punch air to select yourself") .."\n".. 555 S("Place: Incease attachment Y offset") .."\n".. 556 S("Sneak+Place: Decease attachment Y offset") .."\n".. 557 S("Aux1+Place: Incease attachment rotation") .."\n".. 558 S("Aux1+Sneak+Place: Decrease attachment rotation"), 559 inventory_image = "testtools_object_attacher.png", 560 groups = { testtool = 1, disable_repair = 1 }, 561 on_place = attacher_config, 562 on_secondary_use = attacher_config, 563 on_use = function(itemstack, user, pointed_thing) 564 if user and user:is_player() then 565 local name = user:get_player_name() 566 local selected_object 567 if pointed_thing.type == "object" then 568 selected_object = pointed_thing.ref 569 elseif pointed_thing.type == "nothing" then 570 selected_object = user 571 else 572 return 573 end 574 local ctrl = user:get_player_control() 575 if ctrl.sneak then 576 if selected_object:get_attach() then 577 selected_object:set_detach() 578 minetest.chat_send_player(name, S("Object detached!")) 579 else 580 minetest.chat_send_player(name, S("Object is not attached!")) 581 end 582 return 583 end 584 local parent = ent_parent[name] 585 local child = ent_child[name] 586 local ename = S("<unknown>") 587 if not parent then 588 parent = selected_object 589 ent_parent[name] = parent 590 elseif not child then 591 child = selected_object 592 ent_child[name] = child 593 end 594 local entity = selected_object:get_luaentity() 595 if entity then 596 ename = entity.name 597 elseif selected_object:is_player() then 598 ename = selected_object:get_player_name() 599 end 600 if selected_object == parent then 601 minetest.chat_send_player(name, S("Parent object selected: @1", ename)) 602 elseif selected_object == child then 603 minetest.chat_send_player(name, S("Child object selected: @1", ename)) 604 end 605 if parent and child then 606 if parent == child then 607 minetest.chat_send_player(name, S("Can't attach an object to itself!")) 608 ent_parent[name] = nil 609 ent_child[name] = nil 610 return 611 end 612 local meta = itemstack:get_meta() 613 local y 614 if meta:contains("pos_y") then 615 y = meta:get_int("pos_y") 616 else 617 y = DEFAULT_ATTACH_OFFSET_Y 618 end 619 local rx = meta:get_float("rot_x") or 0 620 local offset = {x=0,y=y,z=0} 621 local angle = {x=rx,y=0,z=0} 622 child:set_attach(parent, "", offset, angle) 623 local check_parent = child:get_attach() 624 if check_parent then 625 minetest.chat_send_player(name, S("Object attached! position=@1, rotation=@2", 626 minetest.pos_to_string(offset), minetest.pos_to_string(angle))) 627 else 628 minetest.chat_send_player(name, S("Attachment failed!")) 629 end 630 ent_parent[name] = nil 631 ent_child[name] = nil 632 end 633 end 634 end, 635}) 636 637-- Use loadstring to parse param as a Lua value 638local function use_loadstring(param, player) 639 -- For security reasons, require 'server' priv, just in case 640 -- someone is actually crazy enough to run this on a public server. 641 local privs = minetest.get_player_privs(player:get_player_name()) 642 if not privs.server then 643 return false, "You need 'server' privilege to change object properties!" 644 end 645 if not param then 646 return false, "Failed: parameter is nil" 647 end 648 --[[ DANGER ZONE ]] 649 -- Interpret string as Lua value 650 local func, errormsg = loadstring("return (" .. param .. ")") 651 if not func then 652 return false, "Failed: " .. errormsg 653 end 654 655 -- Apply sandbox here using setfenv 656 setfenv(func, {}) 657 658 -- Run it 659 local good, errOrResult = pcall(func) 660 if not good then 661 -- A Lua error was thrown 662 return false, "Failed: " .. errOrResult 663 end 664 665 -- errOrResult will be the value 666 return true, errOrResult 667end 668 669minetest.register_on_player_receive_fields(function(player, formname, fields) 670 if not (player and player:is_player()) then 671 return 672 end 673 if formname == "testtools:entity_list" then 674 local name = player:get_player_name() 675 if fields.entity_list then 676 local expl = minetest.explode_textlist_event(fields.entity_list) 677 if expl.type == "DCL" then 678 local pos = vector.add(player:get_pos(), {x=0,y=1,z=0}) 679 selections[name] = expl.index 680 minetest.add_entity(pos, get_entity_list()[expl.index]) 681 return 682 elseif expl.type == "CHG" then 683 selections[name] = expl.index 684 return 685 end 686 elseif fields.spawn and selections[name] then 687 local pos = vector.add(player:get_pos(), {x=0,y=1,z=0}) 688 minetest.add_entity(pos, get_entity_list()[selections[name]]) 689 return 690 end 691 elseif formname == "testtools:object_editor" then 692 local name = player:get_player_name() 693 if fields.object_props then 694 local expl = minetest.explode_textlist_event(fields.object_props) 695 if expl.type == "DCL" or expl.type == "CHG" then 696 property_formspec_index[name] = expl.index 697 698 local props = selected_objects[name]:get_properties() 699 local keys = property_formspec_data[name] 700 if (not property_formspec_index[name]) or (not props) then 701 return 702 end 703 local key = keys[property_formspec_index[name]] 704 editor_formspec_selindex[name] = expl.index 705 editor_formspec(name, selected_objects[name], prop_to_string(props[key]), expl.index) 706 return 707 end 708 end 709 if fields.key_enter_field == "value" or fields.submit then 710 local props = selected_objects[name]:get_properties() 711 local keys = property_formspec_data[name] 712 if (not property_formspec_index[name]) or (not props) then 713 return 714 end 715 local key = keys[property_formspec_index[name]] 716 if not key then 717 return 718 end 719 local success, str = use_loadstring(fields.value, player) 720 if success then 721 props[key] = str 722 else 723 minetest.chat_send_player(name, str) 724 return 725 end 726 selected_objects[name]:set_properties(props) 727 local sel = editor_formspec_selindex[name] 728 editor_formspec(name, selected_objects[name], prop_to_string(props[key]), sel) 729 return 730 end 731 end 732end) 733