1 local S
= minetest
.get_translator("schemedit")
2 local F
= minetest
.formspec_escape
6 -- Directory delimeter fallback (normally comes from builtin)
10 local export_path_full
= table.concat({minetest
.get_worldpath(), "schems"}, DIR_DELIM
)
12 local text_color
= "#D79E9E"
13 local text_color_number
= 0xD79E9E
15 schemedit
.markers
= {}
17 -- [local function] Renumber table
18 local function renumber(t
)
20 for _
, i
in pairs(t
) do
34 local displayed_waypoints
= {}
36 -- Sadly, the probabilities presented in Lua (0-255) are not identical to the REAL probabilities in the
37 -- schematic file (0-127). There are two converter functions to convert from one probability type to another.
38 -- This mod tries to retain the “Lua probability” as long as possible and only switches to “schematic probability”
39 -- on an actual export to a schematic.
41 function schemedit
.lua_prob_to_schematic_prob(lua_prob
)
42 return math
.floor(lua_prob
/ 2)
45 function schemedit
.schematic_prob_to_lua_prob(schematic_prob
)
46 return schematic_prob
* 2
50 -- [function] Add form
51 function schemedit
.add_form(name
, def
)
56 tabs
[#tabs
+ 1] = name
60 -- [function] Generate tabs
61 function schemedit
.generate_tabs(current
)
62 local retval
= "tabheader[0,0;tabs;"
63 for _
, t
in pairs(tabs
) do
65 if f
.tab
~= false and f
.caption
then
66 retval
= retval
..f
.caption
..","
68 if type(current
) ~= "number" and current
== f
.name
then
73 retval
= retval
:sub(1, -2) -- Strip last comma
74 retval
= retval
..";"..current
.."]" -- Close tabheader
78 -- [function] Handle tabs
79 function schemedit
.handle_tabs(pos
, name
, fields
)
80 local tab
= tonumber(fields
.tabs
)
81 if tab
and tabs
[tab
] and forms
[tabs
[tab]]
then
82 schemedit
.show_formspec(pos
, name
, forms
[tabs
[tab]]
.name
)
87 -- [function] Show formspec
88 function schemedit
.show_formspec(pos
, player
, tab
, show
, ...)
90 if type(player
) == "string" then
91 player
= minetest
.get_player_by_name(player
)
93 local name
= player
:get_player_name()
96 if not form_data
[name
] then
100 local form
= forms
[tab
].get(form_data
[name
], pos
, name
, ...)
101 if forms
[tab
].tab
then
102 form
= form
..schemedit
.generate_tabs(tab
)
105 minetest
.show_formspec(name
, "schemedit:"..tab
, form
)
108 -- Update player attribute
109 if forms
[tab
].cache_name
~= false then
110 player
:set_attribute("schemedit:tab", tab
)
113 minetest
.close_formspec(pname
, "schemedit:"..tab
)
118 -- [event] On receive fields
119 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
120 local formname
= formname
:split(":")
122 if formname
[1] == "schemedit" and forms
[formname
[2]]
then
123 local handle
= forms
[formname
[2]]
.handle
124 local name
= player
:get_player_name()
125 if contexts
[name
] then
126 if not form_data
[name
] then
130 if not schemedit
.handle_tabs(contexts
[name
], name
, fields
) and handle
then
131 handle(form_data
[name
], contexts
[name
], name
, fields
)
137 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
138 schemedit
.scan_metadata
= function(pos1
, pos2
)
141 for x
=pos1
.x
, pos2
.x
do
142 for y
=pos1
.y
, pos2
.y
do
143 for z
=pos1
.z
, pos2
.z
do
144 local scanpos
= {x
=x
, y
=y
, z
=z
}
145 local node
= minetest
.get_node_or_nil(scanpos
)
147 local prob
, force_place
148 if node
== nil or node
.name
== "schemedit:void" then
152 local meta
= minetest
.get_meta(scanpos
)
154 prob
= tonumber(meta
:get_string("schemedit_prob")) or 255
155 local fp
= meta
:get_string("schemedit_force_place")
163 local hashpos
= minetest
.hash_node_position(scanpos
)
164 prob_list
[hashpos
] = {
167 force_place
= force_place
,
176 -- Sets probability and force_place metadata of an item.
177 -- Also updates item description.
178 -- The itemstack is updated in-place.
179 local function set_item_metadata(itemstack
, prob
, force_place
)
180 local smeta
= itemstack
:get_meta()
181 local prob_desc
= "\n"..S("Probability: @1", prob
or
182 smeta
:get_string("schemedit_prob") or S("Not Set"))
183 -- Update probability
184 if prob
and prob
>= 0 and prob
< 255 then
185 smeta
:set_string("schemedit_prob", tostring(prob
))
186 elseif prob
and prob
== 255 then
187 -- Clear prob metadata for default probability
189 smeta
:set_string("schemedit_prob", nil)
191 prob_desc
= "\n"..S("Probability: @1", smeta
:get_string("schemedit_prob") or
195 -- Update force place
196 if force_place
== true then
197 smeta
:set_string("schemedit_force_place", "true")
198 elseif force_place
== false then
199 smeta
:set_string("schemedit_force_place", nil)
202 -- Update description
203 local desc
= minetest
.registered_items
[itemstack
:get_name()].description
204 local meta_desc
= smeta
:get_string("description")
205 if meta_desc
and meta_desc
~= "" then
209 local original_desc
= smeta
:get_string("original_description")
210 if original_desc
and original_desc
~= "" then
213 smeta
:set_string("original_description", desc
)
216 local force_desc
= ""
217 if smeta
:get_string("schemedit_force_place") == "true" then
218 force_desc
= "\n"..S("Force placement")
221 desc
= desc
..minetest
.colorize(text_color
, prob_desc
..force_desc
)
223 smeta
:set_string("description", desc
)
232 schemedit
.add_form("main", {
235 get
= function(self
, pos
, name
)
236 local meta
= minetest
.get_meta(pos
):to_table().fields
237 local strpos
= minetest
.pos_to_string(pos
)
238 local hashpos
= minetest
.hash_node_position(pos
)
241 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
242 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Hide border")).."]"
244 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Show border")).."]"
247 -- TODO: Show information regarding volume, pos1, pos2, etc... in formspec
250 label[0.5,-0.1;]]..F(S("Position: @1", strpos
))..[[]
251 label[3,-0.1;]]..F(S("Owner: @1", name
))..[[]
253 field[0.8,1;5,1;name;]]..F(S("Schematic name:"))..[[;]]..F(meta
.schem_name
or "")..[[]
254 button[5.3,0.69;1.2,1;save_name;]]..F(S("Save"))..[[]
255 tooltip[save_name;]]..F(S("Save schematic name"))..[[]
256 field_close_on_enter[name;false]
258 button[0.5,1.5;6,1;export;]]..F(S("Export schematic"))..[[]
259 textarea[0.8,2.5;6.2,5;;]]..F(S("The schematic will be exported as a .mts file and stored in\n@1",
260 export_path_full
.. DIR_DELIM
.. "<name>.mts."))..[[;]
261 field[0.8,7;2,1;x;X size:;]]..meta
.x_size
..[[]
262 field[2.8,7;2,1;y;Y size:;]]..meta
.y_size
..[[]
263 field[4.8,7;2,1;z;Z size:;]]..meta
.z_size
..[[]
264 field_close_on_enter[x;false]
265 field_close_on_enter[y;false]
266 field_close_on_enter[z;false]
268 button[0.5,7.5;3,1;save;Save size]
271 if minetest
.get_modpath("doc") then
272 form
= form
.. "image_button[6.4,-0.2;0.8,0.8;doc_button_icon_lores.png;doc;]" ..
273 "tooltip[doc;"..F(S("Help")).."]"
277 handle
= function(self
, pos
, name
, fields
)
278 local realmeta
= minetest
.get_meta(pos
)
279 local meta
= realmeta
:to_table().fields
280 local hashpos
= minetest
.hash_node_position(pos
)
283 doc
.show_entry(name
, "nodes", "schemedit:creator", true)
288 if fields
.border
then
289 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
290 schemedit
.unmark(pos
)
291 meta
.schem_border
= "false"
294 meta
.schem_border
= "true"
298 -- Save size vector values
299 if (fields
.save
or fields
.key_enter_field
== "x" or
300 fields
.key_enter_field
== "y" or fields
.key_enter_field
== "z")
301 and (fields
.x
and fields
.y
and fields
.z
and fields
.x
~= ""
302 and fields
.y
~= "" and fields
.z
~= "") then
303 local x
, y
, z
= tonumber(fields
.x
), tonumber(fields
.y
), tonumber(fields
.z
)
306 meta
.x_size
= math
.max(x
, 1)
309 meta
.y_size
= math
.max(y
, 1)
312 meta
.z_size
= math
.max(z
, 1)
316 -- Save schematic name
317 if fields
.save_name
or fields
.key_enter_field
== "name" and fields
.name
and
318 fields
.name
~= "" then
319 meta
.schem_name
= fields
.name
323 if fields
.export
and meta
.schem_name
and meta
.schem_name
~= "" then
324 local pos1
, pos2
= schemedit
.size(pos
)
325 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
326 local path
= export_path_full
.. DIR_DELIM
329 local plist
= schemedit
.scan_metadata(pos1
, pos2
)
330 local probability_list
= {}
331 for hash
, i
in pairs(plist
) do
332 local prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
)
333 if i
.force_place
== true then
337 table.insert(probability_list
, {
338 pos
= minetest
.get_position_from_hash(hash
),
343 local slist
= minetest
.deserialize(meta
.slices
)
344 local slice_list
= {}
345 for _
, i
in pairs(slist
) do
346 slice_list
[#slice_list
+ 1] = {
347 ypos
= pos
.y
+ i
.ypos
,
348 prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
),
352 local filepath
= path
..meta
.schem_name
..".mts"
353 local res
= minetest
.create_schematic(pos1
, pos2
, probability_list
, filepath
, slice_list
)
356 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
357 S("Exported schematic to @1", filepath
)))
359 minetest
.chat_send_player(name
, minetest
.colorize("red",
360 S("Failed to export schematic to @1", filepath
)))
364 -- Save meta before updating visuals
365 local inv
= realmeta
:get_inventory():get_lists()
366 realmeta
:from_table({fields
= meta
, inventory
= inv
})
369 if not fields
.border
and meta
.schem_border
== "true" then
374 if not fields
.quit
then
375 schemedit
.show_formspec(pos
, minetest
.get_player_by_name(name
), "main")
380 schemedit
.add_form("slice", {
381 caption
= S("Y Slices"),
383 get
= function(self
, pos
, name
, visible_panel
)
384 local meta
= minetest
.get_meta(pos
):to_table().fields
386 self
.selected
= self
.selected
or 1
387 local selected
= tostring(self
.selected
)
388 local slice_list
= minetest
.deserialize(meta
.slices
)
390 for _
, i
in pairs(slice_list
) do
391 local insert
= F(S("Y = @1; Probability = @2", tostring(i
.ypos
), tostring(i
.prob
)))
392 slices
= slices
..F(insert
)..","
394 slices
= slices
:sub(1, -2) -- Remove final comma
398 table[0,0;6.8,6;slices;]]..slices
..[[;]]..selected
..[[]
401 if self
.panel_add
or self
.panel_edit
then
402 local ypos_default
, prob_default
= "", ""
403 local done_button
= "button[5,7.18;2,1;done_add;"..F(S("Done")).."]"
404 if self
.panel_edit
then
405 done_button
= "button[5,7.18;2,1;done_edit;"..F(S("Done")).."]"
406 if slice_list
[self
.selected
] then
407 ypos_default
= slice_list
[self
.selected
].ypos
408 prob_default
= slice_list
[self
.selected
].prob
413 field[0.3,7.5;2.5,1;ypos;]]..F(S("Y position (max. @1):", (meta
.y_size
- 1)))..[[;]]..ypos_default
..[[]
414 field[2.8,7.5;2.5,1;prob;]]..F(S("Probability (0-255):"))..[[;]]..prob_default
..[[]
415 field_close_on_enter[ypos;false]
416 field_close_on_enter[prob;false]
420 if not self
.panel_edit
then
421 form
= form
.."button[0,6;2,1;add;"..F(S("+ Add slice")).."]"
424 if slices
~= "" and self
.selected
and not self
.panel_add
then
425 if not self
.panel_edit
then
427 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
428 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
432 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
433 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
440 handle
= function(self
, pos
, name
, fields
)
441 local meta
= minetest
.get_meta(pos
)
442 local player
= minetest
.get_player_by_name(name
)
444 if fields
.slices
then
445 local slices
= fields
.slices
:split(":")
446 self
.selected
= tonumber(slices
[2])
450 if not self
.panel_add
then
451 self
.panel_add
= true
452 schemedit
.show_formspec(pos
, player
, "slice")
455 schemedit
.show_formspec(pos
, player
, "slice")
459 local ypos
, prob
= tonumber(fields
.ypos
), tonumber(fields
.prob
)
460 if (fields
.done_add
or fields
.done_edit
) and fields
.ypos
and fields
.prob
and
461 fields
.ypos
~= "" and fields
.prob
~= "" and ypos
and prob
and
462 ypos
<= (meta
:get_int("y_size") - 1) and prob
>= 0 and prob
<= 255 then
463 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
464 local index
= #slice_list
+ 1
465 if fields
.done_edit
then
466 index
= self
.selected
469 slice_list
[index
] = {ypos
= ypos
, prob
= prob
}
471 meta
:set_string("slices", minetest
.serialize(slice_list
))
473 -- Update and show formspec
475 schemedit
.show_formspec(pos
, player
, "slice")
478 if fields
.remove and self
.selected
then
479 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
480 slice_list
[self
.selected
] = nil
481 meta
:set_string("slices", minetest
.serialize(renumber(slice_list
)))
485 self
.panel_edit
= nil
486 schemedit
.show_formspec(pos
, player
, "slice")
490 if not self
.panel_edit
then
491 self
.panel_edit
= true
492 schemedit
.show_formspec(pos
, player
, "slice")
494 self
.panel_edit
= nil
495 schemedit
.show_formspec(pos
, player
, "slice")
501 schemedit
.add_form("probtool", {
503 caption
= S("Schematic Node Probability Tool"),
504 get
= function(self
, pos
, name
)
505 local player
= minetest
.get_player_by_name(name
)
509 local probtool
= player
:get_wielded_item()
510 if probtool
:get_name() ~= "schemedit:probtool" then
514 local meta
= probtool
:get_meta()
515 local prob
= tonumber(meta
:get_string("schemedit_prob"))
516 local force_place
= meta
:get_string("schemedit_force_place")
521 if force_place
== nil or force_place
== "" then
522 force_place
= "false"
524 local form
= "size[5,4]"..
525 "label[0,0;"..F(S("Schematic Node Probability Tool")).."]"..
526 "field[0.75,1;4,1;prob;"..F(S("Probability (0-255)"))..";"..prob
.."]"..
527 "checkbox[0.60,1.5;force_place;"..F(S("Force placement"))..";" .. force_place
.. "]" ..
528 "button_exit[0.25,3;2,1;cancel;"..F(S("Cancel")).."]"..
529 "button_exit[2.75,3;2,1;submit;"..F(S("Apply")).."]"..
530 "tooltip[prob;"..F(S("Probability that the node will be placed")).."]"..
531 "tooltip[force_place;"..F(S("If enabled, the node will replace nodes other than air and ignore")).."]"..
532 "field_close_on_enter[prob;false]"
535 handle
= function(self
, pos
, name
, fields
)
536 if fields
.submit
then
537 local prob
= tonumber(fields
.prob
)
539 local player
= minetest
.get_player_by_name(name
)
543 local probtool
= player
:get_wielded_item()
544 if probtool
:get_name() ~= "schemedit:probtool" then
548 local force_place
= self
.force_place
== true
550 set_item_metadata(probtool
, prob
, force_place
)
552 player
:set_wielded_item(probtool
)
555 if fields
.force_place
== "true" then
556 self
.force_place
= true
557 elseif fields
.force_place
== "false" then
558 self
.force_place
= false
567 --- Copies and modifies positions `pos1` and `pos2` so that each component of
568 -- `pos1` is less than or equal to the corresponding component of `pos2`.
569 -- Returns the new positions.
570 function schemedit
.sort_pos(pos1
, pos2
)
571 if not pos1
or not pos2
then
575 pos1
, pos2
= table.copy(pos1
), table.copy(pos2
)
576 if pos1
.x
> pos2
.x
then
577 pos2
.x
, pos1
.x
= pos1
.x
, pos2
.x
579 if pos1
.y
> pos2
.y
then
580 pos2
.y
, pos1
.y
= pos1
.y
, pos2
.y
582 if pos1
.z
> pos2
.z
then
583 pos2
.z
, pos1
.z
= pos1
.z
, pos2
.z
588 -- [function] Prepare size
589 function schemedit
.size(pos
)
590 local pos1
= vector
.new(pos
)
591 local meta
= minetest
.get_meta(pos
)
592 local node
= minetest
.get_node(pos
)
593 local param2
= node
.param2
595 x
= meta
:get_int("x_size"),
596 y
= math
.max(meta
:get_int("y_size") - 1, 0),
597 z
= meta
:get_int("z_size"),
601 local new_pos
= vector
.add({x
= size
.z
, y
= size
.y
, z
= -size
.x
}, pos
)
603 new_pos
.z
= new_pos
.z
+ 1
605 elseif param2
== 2 then
606 local new_pos
= vector
.add({x
= -size
.x
, y
= size
.y
, z
= -size
.z
}, pos
)
608 new_pos
.x
= new_pos
.x
+ 1
610 elseif param2
== 3 then
611 local new_pos
= vector
.add({x
= -size
.z
, y
= size
.y
, z
= size
.x
}, pos
)
613 new_pos
.z
= new_pos
.z
- 1
616 local new_pos
= vector
.add(size
, pos
)
618 new_pos
.x
= new_pos
.x
- 1
623 -- [function] Mark region
624 function schemedit
.mark(pos
)
625 schemedit
.unmark(pos
)
627 local id
= minetest
.hash_node_position(pos
)
628 local owner
= minetest
.get_meta(pos
):get_string("owner")
629 local pos1
, pos2
= schemedit
.size(pos
)
630 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
632 local thickness
= 0.2
633 local sizex
, sizey
, sizez
= (1 + pos2
.x
- pos1
.x
) / 2, (1 + pos2
.y
- pos1
.y
) / 2, (1 + pos2
.z
- pos1
.z
) / 2
639 for _
, z
in ipairs({pos1
.z
- 0.5, pos2
.z
+ 0.5}) do
645 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= pos1
.y
+ sizey
- 0.5, z
= z
+ offset
}, "schemedit:display")
646 if marker
~= nil then
647 marker
:set_properties({
648 visual_size
={x
=(sizex
+0.01) * 2, y
=sizey
* 2},
650 marker
:get_luaentity().id
= id
651 marker
:get_luaentity().owner
= owner
652 table.insert(m
, marker
)
659 for _
, x
in ipairs({pos1
.x
- 0.5, pos2
.x
+ 0.5}) do
666 local marker
= minetest
.add_entity({x
= x
+ offset
, y
= pos1
.y
+ sizey
- 0.5, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
667 if marker
~= nil then
668 marker
:set_properties({
669 visual_size
={x
=(sizez
+0.01) * 2, y
=sizey
* 2},
671 marker
:set_yaw(math
.pi
/ 2)
672 marker
:get_luaentity().id
= id
673 marker
:get_luaentity().owner
= owner
674 table.insert(m
, marker
)
679 schemedit
.markers
[id
] = m
683 -- [function] Unmark region
684 function schemedit
.unmark(pos
)
685 local id
= minetest
.hash_node_position(pos
)
686 if schemedit
.markers
[id
] then
688 for _
, entity
in ipairs(schemedit
.markers
[id
]) do
697 --- Mark node probability values near player
700 -- Show probability and force_place status of a particular position for player in HUD.
701 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
702 -- The distance to the node is also displayed below that. This can't be avoided and is
703 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
704 function schemedit
.display_node_prob(player
, pos
, prob
, force_place
)
706 if prob
and force_place
== true then
707 wpstring
= string.format("%d [F]", prob
)
710 elseif force_place
== true then
714 return player
:hud_add({
715 hud_elem_type
= "waypoint",
717 text
= "m", -- For the distance artifact
718 number = text_color_number
,
724 -- Display the node probabilities and force_place status of the nodes in a region.
725 -- By default, this is done for nodes near the player (distance: 5).
726 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
727 function schemedit
.display_node_probs_region(player
, pos1
, pos2
)
728 local playername
= player
:get_player_name()
729 local pos
= vector
.round(player
:get_pos())
732 -- Default: 5 nodes away from player in any direction
734 pos1
= vector
.subtract(pos
, dist
)
737 pos2
= vector
.add(pos
, dist
)
739 for x
=pos1
.x
, pos2
.x
do
740 for y
=pos1
.y
, pos2
.y
do
741 for z
=pos1
.z
, pos2
.z
do
742 local checkpos
= {x
=x
, y
=y
, z
=z
}
743 local nodehash
= minetest
.hash_node_position(checkpos
)
745 -- If node is already displayed, remove it so it can re replaced later
746 if displayed_waypoints
[playername
][nodehash
] then
747 player
:hud_remove(displayed_waypoints
[playername
][nodehash
])
748 displayed_waypoints
[playername
][nodehash
] = nil
751 local prob
, force_place
752 local meta
= minetest
.get_meta(checkpos
)
753 prob
= tonumber(meta
:get_string("schemedit_prob"))
754 force_place
= meta
:get_string("schemedit_force_place") == "true"
755 local hud_id
= schemedit
.display_node_prob(player
, checkpos
, prob
, force_place
)
757 displayed_waypoints
[playername
][nodehash
] = hud_id
758 displayed_waypoints
[playername
].display_active
= true
765 -- Remove all active displayed node statuses.
766 function schemedit
.clear_displayed_node_probs(player
)
767 local playername
= player
:get_player_name()
768 for nodehash
, hud_id
in pairs(displayed_waypoints
[playername
]) do
769 player
:hud_remove(hud_id
)
770 displayed_waypoints
[playername
][nodehash
] = nil
771 displayed_waypoints
[playername
].display_active
= false
775 minetest
.register_on_joinplayer(function(player
)
776 displayed_waypoints
[player
:get_player_name()] = {
777 display_active
= false -- If true, there *might* be at least one active node prob HUD display
778 -- If false, no node probabilities are displayed for sure.
782 minetest
.register_on_leaveplayer(function(player
)
783 displayed_waypoints
[player
:get_player_name()] = nil
786 -- Regularily clear the displayed node probabilities and force_place
787 -- for all players who do not wield the probtool.
788 -- This makes sure the screen is not spammed with information when it
791 minetest
.register_globalstep(function(dtime
)
792 cleartimer
= cleartimer
+ dtime
793 if cleartimer
> 2 then
794 local players
= minetest
.get_connected_players()
795 for p
= 1, #players
do
796 local player
= players
[p
]
797 local pname
= player
:get_player_name()
798 if displayed_waypoints
[pname
].display_active
then
799 local item
= player
:get_wielded_item()
800 if item
:get_name() ~= "schemedit:probtool" then
801 schemedit
.clear_displayed_node_probs(player
)
813 -- [priv] schematic_override
814 minetest
.register_privilege("schematic_override", {
815 description
= S("Allows you to access schemedit nodes not owned by you"),
816 give_to_singleplayer
= false,
819 -- [node] Schematic creator
820 minetest
.register_node("schemedit:creator", {
821 description
= S("Schematic Creator"),
822 _doc_items_longdesc
= S("The schematic creator is used to save a region of the world into a schematic file (.mts)."),
823 _doc_items_usagehelp
= S("To get started, place the block facing directly in front of any bottom left corner of the structure you want to save. This block can only be accessed by the placer or by anyone with the “schematic_override” privilege.").."\n"..
824 S("To save a region, rightclick the block, enter the size, a schematic name and hit “Export schematic”. The file will always be saved in the world directory. Note you can use this name in the /placeschem command to place the schematic again.").."\n\n"..
825 S("The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.").."\n\n"..
826 S("Y slices are used to remove entire slices based on chance. For each slice of the schematic region along the Y axis, you can specify that it occours only with a certain chance. In the Y slice tab, you have to specify the Y slice height (0 = bottom) and a probability from 0 to 255 (255 is for 100%). By default, all Y slices occour always.").."\n\n"..
827 S("With a schematic node probability tool, you can set a probability for each node and enable them to overwrite all nodes when placed as schematic. This tool must be used prior to the file export."),
828 tiles
= {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
829 "schemedit_creator_sides.png"},
830 groups
= { dig_immediate
= 2},
831 paramtype2
= "facedir",
832 is_ground_content
= false,
834 after_place_node
= function(pos
, player
)
835 local name
= player
:get_player_name()
836 local meta
= minetest
.get_meta(pos
)
838 meta
:set_string("owner", name
)
839 meta
:set_string("infotext", S("Schematic Creator").."\n"..S("(owned by @1)", name
))
840 meta
:set_string("prob_list", minetest
.serialize({}))
841 meta
:set_string("slices", minetest
.serialize({}))
843 local node
= minetest
.get_node(pos
)
844 local dir
= minetest
.facedir_to_dir(node
.param2
)
846 meta
:set_int("x_size", 1)
847 meta
:set_int("y_size", 1)
848 meta
:set_int("z_size", 1)
850 -- Don't take item from itemstack
853 can_dig
= function(pos
, player
)
854 local name
= player
:get_player_name()
855 local meta
= minetest
.get_meta(pos
)
856 if meta
:get_string("owner") == name
or
857 minetest
.check_player_privs(player
, "schematic_override") == true then
863 on_rightclick
= function(pos
, node
, player
)
864 local meta
= minetest
.get_meta(pos
)
865 local name
= player
:get_player_name()
866 if meta
:get_string("owner") == name
or
867 minetest
.check_player_privs(player
, "schematic_override") == true then
868 -- Get player attribute
869 local tab
= player
:get_attribute("schemedit:tab")
870 if not forms
[tab
] or not tab
then
874 schemedit
.show_formspec(pos
, player
, tab
, true)
877 after_destruct
= function(pos
)
878 schemedit
.unmark(pos
)
882 minetest
.register_tool("schemedit:probtool", {
883 description
= S("Schematic Node Probability Tool"),
884 _doc_items_longdesc
=
885 S("This is an advanced tool which only makes sense when used together with a schematic creator. It is used to finetune the way how nodes from a schematic are placed.").."\n"..
886 S("It allows you to set two things:").."\n"..
887 S("1) Set probability: Chance for any particular node to be actually placed (default: always placed)").."\n"..
888 S("2) Enable force placement: These nodes replace node other than air and ignored when placed in a schematic (default: off)"),
889 _doc_items_usagehelp
= "\n"..
890 S("BASIC USAGE:").."\n"..
891 S("Punch to configure the tool. Select a probability (0-255; 255 is for 100%) and enable or disable force placement. Now place the tool on any node to apply these values to the node. This information is preserved in the node until it is destroyed or changed by the tool again. This tool has no effect on schematic voids.").."\n"..
892 S("Now you can use a schematic creator to save a region as usual, the nodes will now be saved with the special node settings applied.").."\n\n"..
893 S("NODE HUD:").."\n"..
894 S("To help you remember the node values, the nodes with special values are labelled in the HUD. The first line shows probability and force placement (with “[F]”). The second line is the current distance to the node. Nodes with default settings and schematic voids are not labelled.").."\n"..
895 S("To disable the node HUD, unselect the tool or hit “place” while not pointing anything.").."\n\n"..
896 S("UPDATING THE NODE HUD:").."\n"..
897 S("The node HUD is not updated automatically any may be outdated. The node HUD only updates the HUD for nodes close to you whenever you place the tool or press the punch and sneak keys simutanously. If you sneak-punch a schematic creator, then the node HUd is updated for all nodes within the schematic creator's region, even if this region is very big."),
898 wield_image
= "schemedit_probtool.png",
899 inventory_image
= "schemedit_probtool.png",
900 liquids_pointable
= true,
901 on_use
= function(itemstack
, user
, pointed_thing
)
902 local ctrl
= user
:get_player_control()
904 if not ctrl
.sneak
then
905 -- Open dialog to change the probability to apply to nodes
906 schemedit
.show_formspec(user
:get_pos(), user
, "probtool", true)
910 -- Display the probability and force_place values for nodes.
912 -- If a schematic creator was punched, only enable display for all nodes
913 -- within the creator's region.
914 local use_creator_region
= false
915 if pointed_thing
and pointed_thing
.type == "node" and pointed_thing
.under
then
916 punchpos
= pointed_thing
.under
917 local node
= minetest
.get_node(punchpos
)
918 if node
.name
== "schemedit:creator" then
919 local pos1
, pos2
= schemedit
.size(punchpos
)
920 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
921 schemedit
.display_node_probs_region(user
, pos1
, pos2
)
926 -- Otherwise, just display the region close to the player
927 schemedit
.display_node_probs_region(user
)
930 on_secondary_use
= function(itemstack
, user
, pointed_thing
)
931 schemedit
.clear_displayed_node_probs(user
)
933 -- Set note probability and force_place and enable node probability display
934 on_place
= function(itemstack
, placer
, pointed_thing
)
935 -- Use pointed node's on_rightclick function first, if present
936 local node
= minetest
.get_node(pointed_thing
.under
)
937 if placer
and not placer
:get_player_control().sneak
then
938 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
939 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
943 -- This sets the node probability of pointed node to the
944 -- currently used probability stored in the tool.
945 local pos
= pointed_thing
.under
946 local node
= minetest
.get_node(pos
)
947 -- Schematic void are ignored, they always have probability 0
948 if node
.name
== "schemedit:void" then
951 local nmeta
= minetest
.get_meta(pos
)
952 local imeta
= itemstack
:get_meta()
953 local prob
= tonumber(imeta
:get_string("schemedit_prob"))
954 local force_place
= imeta
:get_string("schemedit_force_place")
956 if not prob
or prob
== 255 then
957 nmeta
:set_string("schemedit_prob", nil)
959 nmeta
:set_string("schemedit_prob", prob
)
961 if force_place
== "true" then
962 nmeta
:set_string("schemedit_force_place", "true")
964 nmeta
:set_string("schemedit_force_place", nil)
967 -- Enable node probablity display
968 schemedit
.display_node_probs_region(placer
)
974 minetest
.register_node("schemedit:void", {
975 description
= S("Schematic Void"),
976 _doc_items_longdesc
= S("This is an utility block used in the creation of schematic files. It should be used together with a schematic creator. When saving a schematic, all nodes with a schematic void will be left unchanged when the schematic is placed again. Technically, this is equivalent to a block with the node probability set to 0."),
977 _doc_items_usagehelp
= S("Just place the schematic void like any other block and use the schematic creator to save a portion of the world."),
978 tiles
= { "schemedit_void.png" },
979 drawtype
= "nodebox",
980 is_ground_content
= false,
983 sunlight_propagates
= true,
987 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
990 groups
= { dig_immediate
= 3},
993 -- [entity] Visible schematic border
994 minetest
.register_entity("schemedit:display", {
995 visual
= "upright_sprite",
996 textures
= {"schemedit_border.png"},
997 visual_size
= {x
=10, y
=10},
998 collisionbox
= {0,0,0,0,0,0},
1001 on_step
= function(self
, dtime
)
1003 self
.object
:remove()
1004 elseif not schemedit
.markers
[self
.id
] then
1005 self
.object
:remove()
1008 on_activate
= function(self
)
1009 self
.object
:set_armor_groups({immortal
= 1})
1013 -- [chatcommand] Place schematic
1014 minetest
.register_chatcommand("placeschem", {
1015 description
= S("Place schematic at the position specified or the current player position (loaded from @1.)", export_path_full
),
1016 privs
= {debug
= true},
1017 params
= S("<schematic name>[.mts] [<x> <y> <z>]"),
1018 func
= function(name
, param
)
1019 local schem
, p
= string.match(param
, "^([^ ]+) *(.*)$")
1020 local pos
= minetest
.string_to_pos(p
)
1023 return false, S("No schematic file specified.")
1027 pos
= minetest
.get_player_by_name(name
):get_pos()
1030 -- Automatiically add file name suffix if omitted
1032 if string.sub(schem
, string.len(schem
)-3, string.len(schem
)) == ".mts" then
1035 schem_full
= schem
.. ".mts"
1038 local success
= minetest
.place_schematic(pos
, export_path_full
.. DIR_DELIM
.. schem_full
, "random", nil, false)
1040 if success
== nil then
1041 return false, S("Schematic file could not be loaded!")