4 ITB (insidethebox) minetest game - Copyright (C) 2017-2018 sofar & nore
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public License
8 as published by the Free Software Foundation; either version 2.1
9 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free
18 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
23 local S
= minetest
.get_translator("tools")
28 minetest
.register_item(":", {
30 wield_image
= "wieldhand.png",
31 inventory_image
= "wieldhand.png",
32 wield_scale
= {x
= 1, y
= 1, z
= 2},
35 full_punch_interval
= 0.5,
37 -- can't pick up any node by hand
39 hand
= {times
={[1] = 0.2}, uses
= 0, maxlevel
= 1},
43 groups
= {not_in_creative_inventory
= 1}
46 minetest
.register_item("tools:edit", {
48 wield_image
= "wieldhand_edit.png",
49 inventory_image
= "wieldhand_edit.png",
50 wield_scale
= {x
= 1, y
= 1, z
= 2},
51 range
= 4, -- to make sure we can reach as far as the player
53 full_punch_interval
= 0.1,
56 hand
= {times
={[1] = 0.2}, uses
= 0, maxlevel
= 1},
57 axe
= {times
={[1] = 0.2}, uses
= 0, maxlevel
= 1},
58 shovel
= {times
={[1] = 0.2}, uses
= 0, maxlevel
= 1},
59 pickaxe
= {times
={[1] = 0.2}, uses
= 0, maxlevel
= 1},
60 node
= {times
={[1] = 0.2}, uses
= 0, maxlevel
= 1},
61 unbreakable
= {times
={[1] = 0.2}, uses
= 0, maxlevel
= 1},
65 groups
= {not_in_creative_inventory
= 1}
70 minetest
.register_tool("tools:shovel", {
71 description
= S("Shovel"),
72 inventory_image
= "shovel.png",
76 times
= {[1] = 1.5, [2] = 1.5, [3] = 1.5},
82 frame
.register("tools:shovel")
84 minetest
.register_tool("tools:axe", {
85 description
= S("Axe"),
86 inventory_image
= "axe.png",
90 times
= {[1] = 1.5, [2] = 1.5, [3] = 1.5},
96 frame
.register("tools:axe")
98 minetest
.register_tool("tools:pickaxe", {
99 description
= S("Pickaxe"),
100 inventory_image
= "pickaxe.png",
101 tool_capabilities
= {
104 times
= {[1] = 3, [2] = 3, [3] = 3},
110 frame
.register("tools:pickaxe")
112 minetest
.register_tool("tools:sword", {
113 description
= S("Sword"),
114 inventory_image
= "sword.png",
116 frame
.register("tools:sword")
119 minetest
.register_tool("tools:flint_and_steel", {
120 description
= S("Flint and steel"),
121 inventory_image
= "flint_and_steel.png",
123 frame
.register("tools:flint_and_steel")
125 local function node_dig(pos
, digger
)
126 -- from builtin/item.lua core.node_dig():
127 local node
= minetest
.get_node(pos
)
128 local def
= minetest
.registered_nodes
[node
.name
]
130 local sounds
= minetest
.registered_nodes
[node
.name
].sounds
131 if sounds
and sounds
.dig
then
132 minetest
.sound_play(sounds
.dig
, {pos
= pos
})
135 local oldmetadata
= nil
136 if def
and def
.after_dig_node
then
137 oldmetadata
= core
.get_meta(pos
):to_table()
140 minetest
.remove_node(pos
, node
)
142 if def
and def
.after_dig_node
then
143 local pos_copy
= {x
=pos
.x
, y
=pos
.y
, z
=pos
.z
}
144 local node_copy
= {name
= node
.name
, param1
= node
.param1
, param2
= node
.param2
}
145 def
.after_dig_node(pos_copy
, node_copy
, oldmetadata
, digger
)
148 for _
, callback
in ipairs(core
.registered_on_dignodes
) do
149 local origin
= core
.callback_origins
[callback
]
151 core
.set_last_run_mod(origin
.mod)
154 -- Copy pos and node because callback can modify them
155 local pos_copy
= {x
=pos
.x
, y
=pos
.y
, z
=pos
.z
}
156 local node_copy
= {name
= node
.name
, param1
= node
.param1
, param2
= node
.param2
}
157 callback(pos_copy
, node_copy
, digger
)
161 minetest
.register_tool("tools:admin", {
162 description
= S("Admin remove tool"),
163 inventory_image
= "admin_tool.png",
164 liquids_pointable
= true,
166 on_use
= function(itemstack
, digger
, pointed_thing
)
167 if not pointed_thing
or not pointed_thing
.under
then
170 -- Prevent non-admins from using an admin tool if they ever get one
171 if not digger
or not minetest
.check_player_privs(digger
, "server") then
174 node_dig(pointed_thing
.under
, digger
)
180 minetest
.register_tool("tools:player", {
181 description
= S("Remove tool").."\n"..
182 S("Left-click to remove a node").."\n"..
183 S("Right-click to remove a lot of nodes").."\n"..
184 S("Shift-left-click a placed node to restrict removal"),
185 inventory_image
= "remove_tool.png",
186 liquids_pointable
= true,
188 on_use
= function(itemstack
, digger
, pointed_thing
)
192 local name
= digger
:get_player_name()
193 if not boxes
.players_editing_boxes
[name
] then
196 if not pointed_thing
or not pointed_thing
.under
then
197 if not digger
:get_player_control().sneak
then
200 -- empty the contained node
201 local meta
= itemstack
:get_meta()
202 meta
:set_string("nodes", "return {}")
203 minetest
.chat_send_player(name
, S("Remove tool unrestricted"))
204 meta
:set_string("description", S("Remove tool").."\n"..
205 S("Left-click to remove a node").."\n"..
206 S("Right-click to remove a lot of nodes").."\n"..
207 S("Shift-left-click a placed node to restrict removal"))
210 if digger
:get_player_control().sneak
then
211 -- fill the itemstack
212 local meta
= itemstack
:get_meta()
213 local nodes
= minetest
.deserialize(meta
:get_string("nodes") or {}) or {}
214 local node
= minetest
.get_node(pointed_thing
.under
)
215 nodes
[#nodes
+ 1] = node
.name
216 meta
:set_string("nodes", minetest
.serialize(nodes
))
217 minetest
.chat_send_player(name
, S("Remove tool restricted to: @1", table.concat(nodes
, ", ")))
218 meta
:set_string("description", S("Remove tool").."\n"..S("Will remove: @1", table.concat(nodes
, ", ")))
222 local box
= boxes
.players_editing_boxes
[name
]
223 local pos
= pointed_thing
.under
224 if pos
.x
<= box
.minp
.x
or pos
.x
>= box
.maxp
.x
or
225 pos
.y
<= box
.minp
.y
or pos
.y
>= box
.maxp
.y
or
226 pos
.z
<= box
.minp
.z
or pos
.z
>= box
.maxp
.z
230 local meta
= itemstack
:get_meta()
231 local n
= meta
:get_string("nodes")
233 if n
and n
~= "" and n
~= "return {}" then
234 local nodes
= minetest
.deserialize(n
)
235 local t
= minetest
.get_node(pointed_thing
.under
)
236 for _
, v
in ipairs(nodes
) do
238 node_dig(pointed_thing
.under
, digger
)
244 node_dig(pointed_thing
.under
, digger
)
248 on_place
= function(itemstack
, placer
, pointed_thing
)
249 if not pointed_thing
or not pointed_thing
.under
then
255 local name
= placer
:get_player_name()
256 if not boxes
.players_editing_boxes
[name
] then
259 local box
= boxes
.players_editing_boxes
[name
]
260 local pos
= pointed_thing
.under
262 local meta
= itemstack
:get_meta()
263 local n
= meta
:get_string("nodes")
265 if n
and n
~= "" and n
~= "return {}" then
266 nodes
= minetest
.deserialize(n
)
269 for x
= pos
.x
- 1, pos
.x
+ 1 do
270 for y
= pos
.y
+ 1, pos
.y
- 1, -1 do
271 for z
= pos
.z
- 1, pos
.z
+ 1 do
272 if x
> box
.minp
.x
and x
< box
.maxp
.x
and
273 y
> box
.minp
.y
and y
< box
.maxp
.y
and
274 z
> box
.minp
.z
and z
< box
.maxp
.z
then
276 node_dig({x
= x
, y
= y
, z
= z
}, placer
)
278 for _
, v
in ipairs(nodes
) do
279 local p
= {x
= x
, y
= y
, z
= z
}
280 local t
= minetest
.get_node(p
)
293 frame
.register("tools:player")
302 minetest
.register_tool("tools:bulk", {
303 description
= S("Bulk placement tool").."\n"..
304 S("Left-click air to toggle mode").."\n"..
305 S("Shift-left-click to toggle mode").."\n"..
306 S("Left-click a node to pick it up").."\n"..
307 S("Right-click to place nodes"),
308 inventory_image
= "node_place_tool.png",
309 liquids_pointable
= true,
311 on_use
= function(itemstack
, placer
, pointed_thing
)
312 if not pointed_thing
or not pointed_thing
.under
or
313 placer
:get_player_control().sneak
then
314 -- toggle placement mode
315 local meta
= itemstack
:get_meta()
316 local mode
= meta
:get_int("mode") or 0
318 if mode
> #bulk_modes
then
321 minetest
.chat_send_player(placer
:get_player_name(),
322 S("Bulk placement tool will place \"@1\".", bulk_modes
[mode
]))
324 meta
:set_int("mode", mode
)
327 local node
= minetest
.get_node(pointed_thing
.under
)
328 local def
= minetest
.registered_nodes
[node
.name
]
329 if not def
and not def
.groups
then
332 if def
.groups
.not_in_creative_inventory
or
333 def
.groups
.trigger
or def
.groups
.mech
or
336 if not minetest
.check_player_privs(placer
:get_player_name(), "server") then
340 itemstack
:set_metadata(minetest
.serialize(node
))
341 minetest
.chat_send_player(placer
:get_player_name(),
342 "Bulk tool will place " .. node
.name
)
343 local meta
= itemstack
:get_meta()
344 meta
:set_string("description", S("Bulk placement tool").."\n"..
345 S("Node: @1", node
.name
).."\n"..
346 S("Mode: @1", bulk_modes
[meta
:get_int("mode")]))
349 on_place
= function(itemstack
, placer
, pointed_thing
)
350 if not pointed_thing
or not pointed_thing
.above
or not placer
then
353 local node
= minetest
.deserialize(itemstack
:get_metadata())
355 minetest
.chat_send_player(placer
:get_player_name(),
356 S("Pick up a node first by left-clicking it with this tool"))
359 local name
= placer
:get_player_name()
360 local box
= boxes
.players_editing_boxes
[name
]
361 if not box
and not minetest
.check_player_privs(name
, "server") then
366 minp
= { x
= -32768, y
= -32768, z
= -32768 },
367 maxp
= { x
= 32768, y
= 32768, z
= 32768 },
371 -- make sure we don't choke the player.
372 local head
= vector
.add(vector
.round(placer
:get_pos()), {x
= 0, y
= 1, z
= 0})
374 local pos
= pointed_thing
.under
375 local pnode
= minetest
.get_node(pos
)
376 if not minetest
.registered_nodes
[pnode
.name
].buildable_to
then
377 pos
= pointed_thing
.above
379 local meta
= itemstack
:get_meta()
380 local mode
= meta
:get_int("mode") or 0
382 local size
= { 3, 20, 28 }
383 if mode
== 2 or mode
== 3 then
387 local function can_place_at(p
, b
, h
)
388 if p
.x
<= b
.minp
.x
or p
.x
>= b
.maxp
.x
or
389 p
.y
<= b
.minp
.y
or p
.y
>= b
.maxp
.y
or
390 p
.z
<= b
.minp
.z
or p
.z
>= b
.maxp
.z
then
393 if vector
.equals(p
, h
) then
396 local n
= minetest
.get_node(p
)
397 if n
.name
== "air" then
400 local def
= minetest
.registered_nodes
[n
.name
]
401 if def
and def
.liquidtype
and def
.liquidtype
== "flowing" then
407 if mode
== 0 or mode
== 2 then
408 for x
= pos
.x
- size
[1], pos
.x
+ size
[1] do
409 for y
= pos
.y
- size
[1], pos
.y
+ size
[1] do
410 for z
= pos
.z
- size
[1], pos
.z
+ size
[1] do
411 local ppos
= {x
= x
, y
= y
, z
= z
}
412 if can_place_at(ppos
, box
, head
) and
413 vector
.distance(pos
, ppos
) <=
414 (math
.random(size
[2], size
[3]) / 10)
416 minetest
.set_node(ppos
, node
)
421 elseif mode
== 1 or mode
== 3 then
422 for x
= pos
.x
- size
[1], pos
.x
+ size
[1] do
423 for z
= pos
.z
- size
[1], pos
.z
+ size
[1] do
424 local ppos
= {x
= x
, y
= pos
.y
, z
= z
}
425 if can_place_at(ppos
, box
, head
) and
426 vector
.distance(pos
, ppos
) <=
427 (math
.random(size
[2], size
[3]) / 10)
429 minetest
.set_node(ppos
, node
)
433 elseif mode
== 4 then
434 local dir
= minetest
.yaw_to_dir(placer
:get_look_yaw() - (math
.pi
/ 2))
437 local ppos
= vector
.round(vector
.add(pos
, vector
.multiply(dir
, i
)))
439 if can_place_at(ppos
, box
, head
) then
440 minetest
.set_node(ppos
, node
)
447 minetest
.log("action", name
.. " uses tools:bulk placing a " .. bulk_modes
[mode
] ..
448 " of " .. node
.name
.. " at " .. minetest
.pos_to_string(pos
))
452 frame
.register("tools:bulk")
454 minetest
.register_tool("tools:paint", {
455 description
= S("Paint tool").."\n"..
456 S("Paint nodes over with a different node").."\n"..
457 S("Left-click to fill the tool with a node").."\n"..
458 S("Right-click to paint"),
459 inventory_image
= "node_paint_tool.png",
460 liquids_pointable
= true,
462 on_use
= function(itemstack
, placer
, pointed_thing
)
463 if not pointed_thing
or not pointed_thing
.under
then
466 local node
= minetest
.get_node(pointed_thing
.under
)
467 local def
= minetest
.registered_nodes
[node
.name
]
468 if not def
and not def
.groups
then
471 if def
.groups
.not_in_creative_inventory
or
474 if not minetest
.check_player_privs(placer
:get_player_name(), "server") then
478 local meta
= itemstack
:get_meta()
479 meta
:set_string("node", minetest
.serialize(node
))
480 meta
:set_string("meta", minetest
.serialize(minetest
.get_meta(pointed_thing
.under
):to_table()))
481 meta
:set_string("description", "Paint tool\nNode: " .. node
.name
)
482 minetest
.chat_send_player(placer
:get_player_name(),
483 "Paint tool will paint " .. node
.name
)
486 on_place
= function(itemstack
, placer
, pointed_thing
)
487 if not pointed_thing
or not pointed_thing
.above
or not placer
then
490 local meta
= itemstack
:get_meta()
491 local node
= minetest
.deserialize(meta
:get_string("node"))
492 local nmeta
= minetest
.deserialize(meta
:get_string("meta"))
494 minetest
.chat_send_player(placer
:get_player_name(),
495 S("Pick up a node first by left-clicking it with this tool"))
498 local name
= placer
:get_player_name()
499 local box
= boxes
.players_editing_boxes
[name
]
500 if not box
and not minetest
.check_player_privs(name
, "server") then
506 minp
= { x
= -32768, y
= -32768, z
= -32768 },
507 maxp
= { x
= 32768, y
= 32768, z
= 32768 },
511 local pos
= pointed_thing
.under
512 if pos
.x
> box
.minp
.x
and pos
.x
< box
.maxp
.x
and
513 pos
.y
> box
.minp
.y
and pos
.y
< box
.maxp
.y
and
514 pos
.z
> box
.minp
.z
and pos
.z
< box
.maxp
.z
then
515 minetest
.set_node(pos
, node
)
518 local pmeta
= minetest
.get_meta(pos
)
519 pmeta
:from_table(nmeta
)
525 frame
.register("tools:paint")
527 minetest
.register_tool("tools:grow", {
528 description
= S("Growth tool"),
529 inventory_image
= "grow_tool.png",
531 on_place
= function(itemstack
, placer
, pointed_thing
)
532 if not pointed_thing
or not pointed_thing
.under
or not placer
then
535 local name
= placer
:get_player_name()
536 local box
= boxes
.players_editing_boxes
[name
]
538 if not minetest
.check_player_privs(placer
, "server") then
542 minp
= { x
= -32768, y
= -32768, z
= -32768 },
543 maxp
= { x
= 32768, y
= 32768, z
= 32768 },
546 local pos
= pointed_thing
.under
547 local nname
= minetest
.get_node(pos
).name
548 if nname
== "nodes:dirt" then
549 for x
= math
.max(box
.minp
.x
+ 1, pos
.x
- 2), math
.min(box
.maxp
.x
- 1, pos
.x
+ 2) do
550 for y
= math
.max(box
.minp
.y
+ 1, pos
.y
), math
.min(box
.maxp
.y
- 1, pos
.y
) do
551 for z
= math
.max(box
.minp
.z
+ 1, pos
.z
- 2), math
.min(box
.maxp
.z
- 1, pos
.z
+ 2) do
552 local ppos
= {x
= x
, y
= y
, z
= z
}
553 if vector
.distance(pos
, ppos
) <= 2.3 then
554 local node
= minetest
.get_node(ppos
)
555 local above_node
= minetest
.get_node({x
= x
, y
= y
+ 1, z
= z
})
556 if node
.name
== "nodes:dirt" and
557 minetest
.registered_nodes
[above_node
.name
] and
558 minetest
.registered_nodes
[above_node
.name
].walkable
== false then
559 node
.name
= "nodes:dirt_with_grass"
560 minetest
.set_node(ppos
, node
)
566 elseif nname
== "nodes:grass" or nname
== "nodes:dirt_with_grass" then
567 -- Grow grass and other things
568 for x
= math
.max(box
.minp
.x
+ 1, pos
.x
- 2), math
.min(box
.maxp
.x
- 1, pos
.x
+ 2) do
569 for y
= math
.max(box
.minp
.y
+ 1, pos
.y
), math
.min(box
.maxp
.y
- 1, pos
.y
) do
570 for z
= math
.max(box
.minp
.z
+ 1, pos
.z
- 2), math
.min(box
.maxp
.z
- 1, pos
.z
+ 2) do
571 local ppos
= {x
= x
, y
= y
, z
= z
}
572 if vector
.distance(pos
, ppos
) <= 2.3 then
573 local node
= minetest
.get_node(ppos
)
574 local pos_above
= {x
= x
, y
= y
+ 1, z
= z
}
575 local above_node
= minetest
.get_node(pos_above
)
576 if (node
.name
== "nodes:grass" or node
.name
== "nodes:dirt_with_grass") and
577 above_node
.name
== "air" and math
.random() < 0.2 then
608 local p
= plants
[math
.random(1, 28)]
609 local nnode
= {name
= "nodes:" .. p
[1], param2
= p
[2]}
610 minetest
.set_node(pos_above
, nnode
)
620 frame
.register("tools:grow")
622 local function dehash_vector(s
)
624 x
= 256 * string.byte(s
, 1) + string.byte(s
, 2) - 32768,
625 y
= 256 * string.byte(s
, 3) + string.byte(s
, 4) - 32768,
626 z
= 256 * string.byte(s
, 5) + string.byte(s
, 6) - 32768,
630 local function particlestream(p
, o
, name
)
631 local d
= vector
.length(o
)
634 minetest
.add_particle({
635 texture
= "glowblock.png",
636 pos
= vector
.add(p
, vector
.multiply(o
, c
/ d
)),
637 velocity
= vector
.divide(o
, d
* 3),
647 minetest
.register_tool("tools:reveal", {
648 description
= S("Reveal tool").."\n"..
649 S("Reveal breakable nodes and placeholder nodes").."\n"..
650 S("Punch a node to see its connections"),
651 inventory_image
= "reveal_tool.png",
652 on_use
= function(itemstack
, digger
, pointed_thing
)
656 local name
= digger
:get_player_name()
657 if not boxes
.players_editing_boxes
[name
] and not minetest
.check_player_privs(digger
, "server") then
660 local box
= boxes
.players_editing_boxes
[name
]
663 if not minetest
.check_player_privs(digger
, "server") then
667 minp
= { x
= -32768, y
= -32768, z
= -32768 },
668 maxp
= { x
= 32768, y
= 32768, z
= 32768 },
673 { x
= 0, y
= 0, z
= -9/16},
674 { x
= 0, y
= 0, z
= 9/16},
675 { x
= 0, y
= -9/16, z
= 0},
676 { x
= 0, y
= 9/16, z
= 0},
677 { x
= -9/16, y
= 0, z
= 0},
678 { x
= 9/16, y
= 0, z
= 0},
681 -- if clicking a node, reveal mech connections
682 if pointed_thing
.under
then
683 local p
= pointed_thing
.under
684 local meta
= minetest
.get_meta(p
)
685 local offsets
= minetest
.deserialize(meta
:get_string("offsets")) or {}
686 for v
, _
in pairs(offsets
) do
687 local o
= dehash_vector(v
)
688 particlestream(p
, o
, name
)
690 local roffsets
= minetest
.deserialize(meta
:get_string("roffsets")) or {}
691 for v
, _
in pairs(roffsets
) do
692 local o
= dehash_vector(v
)
693 particlestream(vector
.add(p
, o
), {x
=-o
.x
, y
=-o
.y
, z
=-o
.z
}, name
)
696 local dn
= digger
:get_player_name()
698 -- reveal callbacks, mech linkage info
699 local n
= minetest
.get_node(p
)
700 if n
and n
.name
and minetest
.registered_nodes
[n
.name
] then
701 local def
= minetest
.registered_nodes
[n
.name
]
702 minetest
.chat_send_player(dn
, minetest
.colorize("#ff8888",
703 "Detailed info for \"" .. def
.description
:gsub("\n.*", "") ..
704 "\" at " .. minetest
.pos_to_string(p
) .. ":"))
706 for so
, _
in pairs(offsets
) do
707 local offs
= dehash_vector(so
)
708 local npos
= vector
.add(p
, offs
)
709 local nname
= minetest
.get_node(npos
).name
710 minetest
.chat_send_player(dn
, minetest
.colorize("#44ff88",
711 "> triggers " .. nname
.. " at " ..
712 minetest
.pos_to_string(npos
) .. " (offset is " ..
713 minetest
.pos_to_string(offs
) .. ")"))
716 for so
, _
in pairs(roffsets
) do
717 local offs
= dehash_vector(so
)
718 local npos
= vector
.add(p
, offs
)
719 local nname
= minetest
.get_node(npos
).name
720 minetest
.chat_send_player(dn
, minetest
.colorize("#8888ff",
721 "> triggered by " .. nname
.. " at " ..
722 minetest
.pos_to_string(npos
) .. " (offset is " ..
723 minetest
.pos_to_string(offs
) .. ")"))
726 local cb
= def
.on_reveal
735 local ppos
= vector
.floor(digger
:get_pos())
737 local needle
= {"group:axe", "group:shovel", "group:pickaxe", "group:hand", "nodes:placeholder"}
740 local poslist
, _
= minetest
.find_nodes_in_area(vector
.subtract(ppos
, d
),
744 for _
, v
in pairs(poslist
) do
746 if v
.x
< box
.maxp
.x
and v
.y
< box
.maxp
.y
and v
.z
< box
.maxp
.z
and
747 v
.x
> box
.minp
.x
and v
.y
> box
.minp
.y
and v
.z
> box
.minp
.z
then
748 nodeslist
[minetest
.pos_to_string(v
)] = 1
756 for k
, _
in pairs(nodeslist
) do
757 local pos
= minetest
.string_to_pos(k
)
758 local node
= minetest
.get_node(pos
)
759 local groups
= minetest
.registered_nodes
[node
.name
] and
760 minetest
.registered_nodes
[node
.name
].groups
765 elseif groups
.shovel
then
766 texture
= "shovel.png"
767 elseif groups
.pickaxe
then
768 texture
= "pickaxe.png"
769 elseif groups
.hand
then
770 texture
= "wieldhand.png"
772 --FIXME leaves somehow don't work?
773 if texture
and vector
.distance(pos
, digger
:get_pos()) < 8 and
774 node
.name
~= "nodes:snow_ledge" then
775 for _
, v
in ipairs(off
) do
776 minetest
.add_particle({
777 pos
= vector
.add(pos
, v
),
787 if node
.name
== "nodes:placeholder" then
788 local meta
= minetest
.get_meta(pos
)
789 local placeable
= meta
:get_string("placeable")
790 if placeable
~= "" then
791 local nodelist
= minetest
.parse_json(placeable
)
792 --FIXME just do the first placeholder, only
793 local n
, _
= next(nodelist
)
794 minetest
.add_particle({
798 texture
= nodes
.get_tiles(n
),
809 frame
.register("tools:reveal")