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