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 -- truncated export path so the server directory structure is not exposed publicly
13 local export_path_trunc
= table.concat({S("<world path>"), "schems"}, DIR_DELIM
)
15 local text_color
= "#D79E9E"
16 local text_color_number
= 0xD79E9E
18 schemedit
.markers
= {}
20 -- [local function] Renumber table
21 local function renumber(t
)
23 for _
, i
in pairs(t
) do
37 local displayed_waypoints
= {}
39 -- Sadly, the probabilities presented in Lua (0-255) are not identical to the REAL probabilities in the
40 -- schematic file (0-127). There are two converter functions to convert from one probability type to another.
41 -- This mod tries to retain the “Lua probability” as long as possible and only switches to “schematic probability”
42 -- on an actual export to a schematic.
44 function schemedit
.lua_prob_to_schematic_prob(lua_prob
)
45 return math
.floor(lua_prob
/ 2)
48 function schemedit
.schematic_prob_to_lua_prob(schematic_prob
)
49 return schematic_prob
* 2
53 -- [function] Add form
54 function schemedit
.add_form(name
, def
)
59 tabs
[#tabs
+ 1] = name
63 -- [function] Generate tabs
64 function schemedit
.generate_tabs(current
)
65 local retval
= "tabheader[0,0;tabs;"
66 for _
, t
in pairs(tabs
) do
68 if f
.tab
~= false and f
.caption
then
69 retval
= retval
..f
.caption
..","
71 if type(current
) ~= "number" and current
== f
.name
then
76 retval
= retval
:sub(1, -2) -- Strip last comma
77 retval
= retval
..";"..current
.."]" -- Close tabheader
81 -- [function] Handle tabs
82 function schemedit
.handle_tabs(pos
, name
, fields
)
83 local tab
= tonumber(fields
.tabs
)
84 if tab
and tabs
[tab
] and forms
[tabs
[tab]]
then
85 schemedit
.show_formspec(pos
, name
, forms
[tabs
[tab]]
.name
)
90 -- [function] Show formspec
91 function schemedit
.show_formspec(pos
, player
, tab
, show
, ...)
93 if type(player
) == "string" then
94 player
= minetest
.get_player_by_name(player
)
96 local name
= player
:get_player_name()
99 if not form_data
[name
] then
103 local form
= forms
[tab
].get(form_data
[name
], pos
, name
, ...)
104 if forms
[tab
].tab
then
105 form
= form
..schemedit
.generate_tabs(tab
)
108 minetest
.show_formspec(name
, "schemedit:"..tab
, form
)
111 -- Update player attribute
112 if forms
[tab
].cache_name
~= false then
113 player
:set_attribute("schemedit:tab", tab
)
116 minetest
.close_formspec(pname
, "schemedit:"..tab
)
121 -- [event] On receive fields
122 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
123 local formname
= formname
:split(":")
125 if formname
[1] == "schemedit" and forms
[formname
[2]]
then
126 local handle
= forms
[formname
[2]]
.handle
127 local name
= player
:get_player_name()
128 if contexts
[name
] then
129 if not form_data
[name
] then
133 if not schemedit
.handle_tabs(contexts
[name
], name
, fields
) and handle
then
134 handle(form_data
[name
], contexts
[name
], name
, fields
)
140 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
141 schemedit
.scan_metadata
= function(pos1
, pos2
)
144 for x
=pos1
.x
, pos2
.x
do
145 for y
=pos1
.y
, pos2
.y
do
146 for z
=pos1
.z
, pos2
.z
do
147 local scanpos
= {x
=x
, y
=y
, z
=z
}
148 local node
= minetest
.get_node_or_nil(scanpos
)
150 local prob
, force_place
151 if node
== nil or node
.name
== "schemedit:void" then
155 local meta
= minetest
.get_meta(scanpos
)
157 prob
= tonumber(meta
:get_string("schemedit_prob")) or 255
158 local fp
= meta
:get_string("schemedit_force_place")
166 local hashpos
= minetest
.hash_node_position(scanpos
)
167 prob_list
[hashpos
] = {
170 force_place
= force_place
,
179 -- Sets probability and force_place metadata of an item.
180 -- Also updates item description.
181 -- The itemstack is updated in-place.
182 local function set_item_metadata(itemstack
, prob
, force_place
)
183 local smeta
= itemstack
:get_meta()
184 local prob_desc
= "\n"..S("Probability: @1", prob
or
185 smeta
:get_string("schemedit_prob") or S("Not Set"))
186 -- Update probability
187 if prob
and prob
>= 0 and prob
< 255 then
188 smeta
:set_string("schemedit_prob", tostring(prob
))
189 elseif prob
and prob
== 255 then
190 -- Clear prob metadata for default probability
192 smeta
:set_string("schemedit_prob", nil)
194 prob_desc
= "\n"..S("Probability: @1", smeta
:get_string("schemedit_prob") or
198 -- Update force place
199 if force_place
== true then
200 smeta
:set_string("schemedit_force_place", "true")
201 elseif force_place
== false then
202 smeta
:set_string("schemedit_force_place", nil)
205 -- Update description
206 local desc
= minetest
.registered_items
[itemstack
:get_name()].description
207 local meta_desc
= smeta
:get_string("description")
208 if meta_desc
and meta_desc
~= "" then
212 local original_desc
= smeta
:get_string("original_description")
213 if original_desc
and original_desc
~= "" then
216 smeta
:set_string("original_description", desc
)
219 local force_desc
= ""
220 if smeta
:get_string("schemedit_force_place") == "true" then
221 force_desc
= "\n"..S("Force placement")
224 desc
= desc
..minetest
.colorize(text_color
, prob_desc
..force_desc
)
226 smeta
:set_string("description", desc
)
234 local import_btn
= ""
235 if minetest
.read_schematic
then
236 import_btn
= "button[0.5,2.5;6,1;import;"..F(S("Import schematic")).."]"
238 schemedit
.add_form("main", {
241 get
= function(self
, pos
, name
)
242 local meta
= minetest
.get_meta(pos
):to_table().fields
243 local strpos
= minetest
.pos_to_string(pos
)
244 local hashpos
= minetest
.hash_node_position(pos
)
247 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
248 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Hide border")).."]"
250 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Show border")).."]"
253 -- TODO: Show information regarding volume, pos1, pos2, etc... in formspec
256 label[0.5,-0.1;]]..F(S("Position: @1", strpos
))..[[]
257 label[3,-0.1;]]..F(S("Owner: @1", name
))..[[]
259 field[0.8,1;5,1;name;]]..F(S("Schematic name:"))..[[;]]..F(meta
.schem_name
or "")..[[]
260 button[5.3,0.69;1.2,1;save_name;]]..F(S("Save"))..[[]
261 tooltip[save_name;]]..F(S("Save schematic name"))..[[]
262 field_close_on_enter[name;false]
264 button[0.5,1.5;6,1;export;]]..F(S("Export schematic")).."]"..
266 textarea[0.8,3.5;6.2,5;;]]..F(S("The schematic will be exported as a .mts file and stored in\n@1",
267 export_path_trunc
.. DIR_DELIM
.. "<name>.mts."))..[[;]
268 field[0.8,7;2,1;x;]]..F(S("X size:"))..[[;]]..meta
.x_size
..[[]
269 field[2.8,7;2,1;y;]]..F(S("Y size:"))..[[;]]..meta
.y_size
..[[]
270 field[4.8,7;2,1;z;]]..F(S("Z size:"))..[[;]]..meta
.z_size
..[[]
271 field_close_on_enter[x;false]
272 field_close_on_enter[y;false]
273 field_close_on_enter[z;false]
275 button[0.5,7.5;3,1;save;]]..F(S("Save size"))..[[]
278 if minetest
.get_modpath("doc") then
279 form
= form
.. "image_button[6.4,-0.2;0.8,0.8;doc_button_icon_lores.png;doc;]" ..
280 "tooltip[doc;"..F(S("Help")).."]"
284 handle
= function(self
, pos
, name
, fields
)
285 local realmeta
= minetest
.get_meta(pos
)
286 local meta
= realmeta
:to_table().fields
287 local hashpos
= minetest
.hash_node_position(pos
)
289 -- Save size vector values
290 if (fields
.x
and fields
.x
~= "") then
291 local x
= tonumber(fields
.x
)
293 meta
.x_size
= math
.max(x
, 1)
296 if (fields
.y
and fields
.y
~= "") then
297 local y
= tonumber(fields
.y
)
299 meta
.y_size
= math
.max(y
, 1)
302 if (fields
.z
and fields
.z
~= "") then
303 local z
= tonumber(fields
.z
)
305 meta
.z_size
= math
.max(z
, 1)
309 -- Save schematic name
311 meta
.schem_name
= fields
.name
315 doc
.show_entry(name
, "nodes", "schemedit:creator", true)
320 if fields
.border
then
321 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
322 schemedit
.unmark(pos
)
323 meta
.schem_border
= "false"
326 meta
.schem_border
= "true"
331 if fields
.export
and meta
.schem_name
and meta
.schem_name
~= "" then
332 local pos1
, pos2
= schemedit
.size(pos
)
333 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
334 local path
= export_path_full
.. DIR_DELIM
337 local plist
= schemedit
.scan_metadata(pos1
, pos2
)
338 local probability_list
= {}
339 for hash
, i
in pairs(plist
) do
340 local prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
)
341 if i
.force_place
== true then
345 table.insert(probability_list
, {
346 pos
= minetest
.get_position_from_hash(hash
),
351 local slist
= minetest
.deserialize(meta
.slices
)
352 local slice_list
= {}
353 for _
, i
in pairs(slist
) do
354 slice_list
[#slice_list
+ 1] = {
355 ypos
= pos
.y
+ i
.ypos
,
356 prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
),
360 local filepath
= path
..meta
.schem_name
..".mts"
361 local res
= minetest
.create_schematic(pos1
, pos2
, probability_list
, filepath
, slice_list
)
364 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
365 S("Exported schematic to @1", filepath
)))
367 minetest
.chat_send_player(name
, minetest
.colorize("red",
368 S("Failed to export schematic to @1", filepath
)))
373 if fields
.import
and meta
.schem_name
and meta
.schem_name
~= "" then
374 if not minetest
.read_schematic
then
378 local node
= minetest
.get_node(pos
)
379 local path
= export_path_full
.. DIR_DELIM
381 local filepath
= path
..meta
.schem_name
..".mts"
382 local schematic
= minetest
.read_schematic(filepath
, {write_yslice_prob
="low"})
383 local success
= false
386 meta
.x_size
= schematic
.size
.x
387 meta
.y_size
= schematic
.size
.y
388 meta
.z_size
= schematic
.size
.z
389 meta
.slices
= minetest
.serialize(schematic
.yslice_prob
)
391 if node
.param2
== 1 then
392 pos1
= vector
.add(pos
, {x
=1,y
=0,z
=-meta
.z_size
+1})
393 elseif node
.param2
== 2 then
394 pos1
= vector
.add(pos
, {x
=-meta
.x_size
+1,y
=0,z
=-meta
.z_size
})
395 elseif node
.param2
== 3 then
396 pos1
= vector
.add(pos
, {x
=-meta
.x_size
,y
=0,z
=0})
398 pos1
= vector
.add(pos
, {x
=0,y
=0,z
=1})
400 schematic
.yslice_prob
= {}
402 success
= minetest
.place_schematic(pos1
, schematic
, "0", nil, true)
405 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
406 S("Imported schematic from @1", filepath
)))
408 minetest
.chat_send_player(name
, minetest
.colorize("red",
409 S("Failed to import schematic from @1", filepath
)))
415 -- Save meta before updating visuals
416 local inv
= realmeta
:get_inventory():get_lists()
417 realmeta
:from_table({fields
= meta
, inventory
= inv
})
420 if not fields
.border
and meta
.schem_border
== "true" then
425 if not fields
.quit
then
426 schemedit
.show_formspec(pos
, minetest
.get_player_by_name(name
), "main")
431 schemedit
.add_form("slice", {
432 caption
= S("Y Slices"),
434 get
= function(self
, pos
, name
, visible_panel
)
435 local meta
= minetest
.get_meta(pos
):to_table().fields
437 self
.selected
= self
.selected
or 1
438 local selected
= tostring(self
.selected
)
439 local slice_list
= minetest
.deserialize(meta
.slices
)
441 for _
, i
in pairs(slice_list
) do
442 local insert
= F(S("Y = @1; Probability = @2", tostring(i
.ypos
), tostring(i
.prob
)))
443 slices
= slices
..insert
..","
445 slices
= slices
:sub(1, -2) -- Remove final comma
449 table[0,0;6.8,6;slices;]]..slices
..[[;]]..selected
..[[]
452 if self
.panel_add
or self
.panel_edit
then
453 local ypos_default
, prob_default
= "", ""
454 local done_button
= "button[5,7.18;2,1;done_add;"..F(S("Done")).."]"
455 if self
.panel_edit
then
456 done_button
= "button[5,7.18;2,1;done_edit;"..F(S("Done")).."]"
457 if slice_list
[self
.selected
] then
458 ypos_default
= slice_list
[self
.selected
].ypos
459 prob_default
= slice_list
[self
.selected
].prob
464 field[0.3,7.5;2.5,1;ypos;]]..F(S("Y position (max. @1):", (meta
.y_size
- 1)))..[[;]]..ypos_default
..[[]
465 field[2.8,7.5;2.5,1;prob;]]..F(S("Probability (0-255):"))..[[;]]..prob_default
..[[]
466 field_close_on_enter[ypos;false]
467 field_close_on_enter[prob;false]
471 if not self
.panel_edit
then
472 form
= form
.."button[0,6;2.4,1;add;"..F(S("+ Add slice")).."]"
475 if slices
~= "" and self
.selected
and not self
.panel_add
then
476 if not self
.panel_edit
then
478 button[2.4,6;2.4,1;remove;]]..F(S("- Remove slice"))..[[]
479 button[4.8,6;2.4,1;edit;]]..F(S("+/- Edit slice"))..[[]
483 button[2.4,6;2.4,1;remove;]]..F(S("- Remove slice"))..[[]
484 button[4.8,6;2.4,1;edit;]]..F(S("+/- Edit slice"))..[[]
491 handle
= function(self
, pos
, name
, fields
)
492 local meta
= minetest
.get_meta(pos
)
493 local player
= minetest
.get_player_by_name(name
)
495 if fields
.slices
then
496 local slices
= fields
.slices
:split(":")
497 self
.selected
= tonumber(slices
[2])
501 if not self
.panel_add
then
502 self
.panel_add
= true
503 schemedit
.show_formspec(pos
, player
, "slice")
506 schemedit
.show_formspec(pos
, player
, "slice")
510 local ypos
, prob
= tonumber(fields
.ypos
), tonumber(fields
.prob
)
511 if (fields
.done_add
or fields
.done_edit
) and fields
.ypos
and fields
.prob
and
512 fields
.ypos
~= "" and fields
.prob
~= "" and ypos
and prob
and
513 ypos
<= (meta
:get_int("y_size") - 1) and prob
>= 0 and prob
<= 255 then
514 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
515 local index
= #slice_list
+ 1
516 if fields
.done_edit
then
517 index
= self
.selected
520 slice_list
[index
] = {ypos
= ypos
, prob
= prob
}
522 meta
:set_string("slices", minetest
.serialize(slice_list
))
524 -- Update and show formspec
526 schemedit
.show_formspec(pos
, player
, "slice")
529 if fields
.remove and self
.selected
then
530 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
531 slice_list
[self
.selected
] = nil
532 meta
:set_string("slices", minetest
.serialize(renumber(slice_list
)))
536 self
.panel_edit
= nil
537 schemedit
.show_formspec(pos
, player
, "slice")
541 if not self
.panel_edit
then
542 self
.panel_edit
= true
543 schemedit
.show_formspec(pos
, player
, "slice")
545 self
.panel_edit
= nil
546 schemedit
.show_formspec(pos
, player
, "slice")
552 schemedit
.add_form("probtool", {
554 caption
= S("Schematic Node Probability Tool"),
555 get
= function(self
, pos
, name
)
556 local player
= minetest
.get_player_by_name(name
)
560 local probtool
= player
:get_wielded_item()
561 if probtool
:get_name() ~= "schemedit:probtool" then
565 local meta
= probtool
:get_meta()
566 local prob
= tonumber(meta
:get_string("schemedit_prob"))
567 local force_place
= meta
:get_string("schemedit_force_place")
572 if force_place
== nil or force_place
== "" then
573 force_place
= "false"
575 local form
= "size[5,4]"..
576 "label[0,0;"..F(S("Schematic Node Probability Tool")).."]"..
577 "field[0.75,1;4,1;prob;"..F(S("Probability (0-255)"))..";"..prob
.."]"..
578 "checkbox[0.60,1.5;force_place;"..F(S("Force placement"))..";" .. force_place
.. "]" ..
579 "button_exit[0.25,3;2,1;cancel;"..F(S("Cancel")).."]"..
580 "button_exit[2.75,3;2,1;submit;"..F(S("Apply")).."]"..
581 "tooltip[prob;"..F(S("Probability that the node will be placed")).."]"..
582 "tooltip[force_place;"..F(S("If enabled, the node will replace nodes other than air and ignore")).."]"..
583 "field_close_on_enter[prob;false]"
586 handle
= function(self
, pos
, name
, fields
)
587 if fields
.submit
then
588 local prob
= tonumber(fields
.prob
)
590 local player
= minetest
.get_player_by_name(name
)
594 local probtool
= player
:get_wielded_item()
595 if probtool
:get_name() ~= "schemedit:probtool" then
599 local force_place
= self
.force_place
== true
601 set_item_metadata(probtool
, prob
, force_place
)
603 -- Repurpose the tool's wear bar to display the set probability
604 probtool
:set_wear(math
.floor(((255-prob
)/255)*65535))
606 player
:set_wielded_item(probtool
)
609 if fields
.force_place
== "true" then
610 self
.force_place
= true
611 elseif fields
.force_place
== "false" then
612 self
.force_place
= false
621 --- Copies and modifies positions `pos1` and `pos2` so that each component of
622 -- `pos1` is less than or equal to the corresponding component of `pos2`.
623 -- Returns the new positions.
624 function schemedit
.sort_pos(pos1
, pos2
)
625 if not pos1
or not pos2
then
629 pos1
, pos2
= table.copy(pos1
), table.copy(pos2
)
630 if pos1
.x
> pos2
.x
then
631 pos2
.x
, pos1
.x
= pos1
.x
, pos2
.x
633 if pos1
.y
> pos2
.y
then
634 pos2
.y
, pos1
.y
= pos1
.y
, pos2
.y
636 if pos1
.z
> pos2
.z
then
637 pos2
.z
, pos1
.z
= pos1
.z
, pos2
.z
642 -- [function] Prepare size
643 function schemedit
.size(pos
)
644 local pos1
= vector
.new(pos
)
645 local meta
= minetest
.get_meta(pos
)
646 local node
= minetest
.get_node(pos
)
647 local param2
= node
.param2
649 x
= meta
:get_int("x_size"),
650 y
= math
.max(meta
:get_int("y_size") - 1, 0),
651 z
= meta
:get_int("z_size"),
655 local new_pos
= vector
.add({x
= size
.z
, y
= size
.y
, z
= -size
.x
}, pos
)
657 new_pos
.z
= new_pos
.z
+ 1
659 elseif param2
== 2 then
660 local new_pos
= vector
.add({x
= -size
.x
, y
= size
.y
, z
= -size
.z
}, pos
)
662 new_pos
.x
= new_pos
.x
+ 1
664 elseif param2
== 3 then
665 local new_pos
= vector
.add({x
= -size
.z
, y
= size
.y
, z
= size
.x
}, pos
)
667 new_pos
.z
= new_pos
.z
- 1
670 local new_pos
= vector
.add(size
, pos
)
672 new_pos
.x
= new_pos
.x
- 1
677 -- [function] Mark region
678 function schemedit
.mark(pos
)
679 schemedit
.unmark(pos
)
681 local id
= minetest
.hash_node_position(pos
)
682 local owner
= minetest
.get_meta(pos
):get_string("owner")
683 local pos1
, pos2
= schemedit
.size(pos
)
684 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
686 local thickness
= 0.2
687 local sizex
, sizey
, sizez
= (1 + pos2
.x
- pos1
.x
) / 2, (1 + pos2
.y
- pos1
.y
) / 2, (1 + pos2
.z
- pos1
.z
) / 2
693 for _
, z
in ipairs({pos1
.z
- 0.5, pos2
.z
+ 0.5}) do
699 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= pos1
.y
+ sizey
- 0.5, z
= z
+ offset
}, "schemedit:display")
700 if marker
~= nil then
701 marker
:set_properties({
702 visual_size
={x
=(sizex
+0.01) * 2, y
=(sizey
+0.01) * 2},
704 marker
:get_luaentity().id
= id
705 marker
:get_luaentity().owner
= owner
706 table.insert(m
, marker
)
713 for _
, x
in ipairs({pos1
.x
- 0.5, pos2
.x
+ 0.5}) do
720 local marker
= minetest
.add_entity({x
= x
+ offset
, y
= pos1
.y
+ sizey
- 0.5, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
721 if marker
~= nil then
722 marker
:set_properties({
723 visual_size
={x
=(sizez
+0.01) * 2, y
=(sizey
+0.01) * 2},
725 marker
:set_rotation({x
=0, y
=math
.pi
/ 2, z
=0})
726 marker
:get_luaentity().id
= id
727 marker
:get_luaentity().owner
= owner
728 table.insert(m
, marker
)
735 for _
, y
in ipairs({pos1
.y
- 0.5, pos2
.y
+ 0.5}) do
742 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= y
+ offset
, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
743 if marker
~= nil then
744 marker
:set_properties({
745 visual_size
={x
=(sizex
+0.01) * 2, y
=(sizez
+0.01) * 2},
747 marker
:set_rotation({x
=math
.pi
/2, y
=0, z
=0})
748 marker
:get_luaentity().id
= id
749 marker
:get_luaentity().owner
= owner
750 table.insert(m
, marker
)
757 schemedit
.markers
[id
] = m
761 -- [function] Unmark region
762 function schemedit
.unmark(pos
)
763 local id
= minetest
.hash_node_position(pos
)
764 if schemedit
.markers
[id
] then
766 for _
, entity
in ipairs(schemedit
.markers
[id
]) do
775 --- Mark node probability values near player
778 -- Show probability and force_place status of a particular position for player in HUD.
779 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
780 -- The distance to the node is also displayed below that. This can't be avoided and is
781 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
782 function schemedit
.display_node_prob(player
, pos
, prob
, force_place
)
784 if prob
and force_place
== true then
785 wpstring
= string.format("%d [F]", prob
)
788 elseif force_place
== true then
792 return player
:hud_add({
793 hud_elem_type
= "waypoint",
795 text
= "m", -- For the distance artifact
796 number = text_color_number
,
802 -- Display the node probabilities and force_place status of the nodes in a region.
803 -- By default, this is done for nodes near the player (distance: 5).
804 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
805 function schemedit
.display_node_probs_region(player
, pos1
, pos2
)
806 local playername
= player
:get_player_name()
807 local pos
= vector
.round(player
:get_pos())
810 -- Default: 5 nodes away from player in any direction
812 pos1
= vector
.subtract(pos
, dist
)
815 pos2
= vector
.add(pos
, dist
)
817 for x
=pos1
.x
, pos2
.x
do
818 for y
=pos1
.y
, pos2
.y
do
819 for z
=pos1
.z
, pos2
.z
do
820 local checkpos
= {x
=x
, y
=y
, z
=z
}
821 local nodehash
= minetest
.hash_node_position(checkpos
)
823 -- If node is already displayed, remove it so it can re replaced later
824 if displayed_waypoints
[playername
][nodehash
] then
825 player
:hud_remove(displayed_waypoints
[playername
][nodehash
])
826 displayed_waypoints
[playername
][nodehash
] = nil
829 local prob
, force_place
830 local meta
= minetest
.get_meta(checkpos
)
831 prob
= tonumber(meta
:get_string("schemedit_prob"))
832 force_place
= meta
:get_string("schemedit_force_place") == "true"
833 local hud_id
= schemedit
.display_node_prob(player
, checkpos
, prob
, force_place
)
835 displayed_waypoints
[playername
][nodehash
] = hud_id
836 displayed_waypoints
[playername
].display_active
= true
843 -- Remove all active displayed node statuses.
844 function schemedit
.clear_displayed_node_probs(player
)
845 local playername
= player
:get_player_name()
846 for nodehash
, hud_id
in pairs(displayed_waypoints
[playername
]) do
847 player
:hud_remove(hud_id
)
848 displayed_waypoints
[playername
][nodehash
] = nil
849 displayed_waypoints
[playername
].display_active
= false
853 minetest
.register_on_joinplayer(function(player
)
854 displayed_waypoints
[player
:get_player_name()] = {
855 display_active
= false -- If true, there *might* be at least one active node prob HUD display
856 -- If false, no node probabilities are displayed for sure.
860 minetest
.register_on_leaveplayer(function(player
)
861 displayed_waypoints
[player
:get_player_name()] = nil
864 -- Regularily clear the displayed node probabilities and force_place
865 -- for all players who do not wield the probtool.
866 -- This makes sure the screen is not spammed with information when it
869 minetest
.register_globalstep(function(dtime
)
870 cleartimer
= cleartimer
+ dtime
871 if cleartimer
> 2 then
872 local players
= minetest
.get_connected_players()
873 for p
= 1, #players
do
874 local player
= players
[p
]
875 local pname
= player
:get_player_name()
876 if displayed_waypoints
[pname
].display_active
then
877 local item
= player
:get_wielded_item()
878 if item
:get_name() ~= "schemedit:probtool" then
879 schemedit
.clear_displayed_node_probs(player
)
891 -- [priv] schematic_override
892 minetest
.register_privilege("schematic_override", {
893 description
= S("Allows you to access schemedit nodes not owned by you"),
894 give_to_singleplayer
= false,
897 -- [node] Schematic creator
898 minetest
.register_node("schemedit:creator", {
899 description
= S("Schematic Creator"),
900 _doc_items_longdesc
= S("The schematic creator is used to save a region of the world into a schematic file (.mts)."),
901 _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"..
902 S("To save a region, use the block, enter the size and 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"..
903 S("The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.").."\n\n"..
904 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 occurs 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 occur always.").."\n\n"..
905 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."),
906 tiles
= {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
907 "schemedit_creator_sides.png"},
908 groups
= { dig_immediate
= 2},
909 paramtype2
= "facedir",
910 is_ground_content
= false,
912 after_place_node
= function(pos
, player
)
913 local name
= player
:get_player_name()
914 local meta
= minetest
.get_meta(pos
)
916 meta
:set_string("owner", name
)
917 meta
:set_string("infotext", S("Schematic Creator").."\n"..S("(owned by @1)", name
))
918 meta
:set_string("prob_list", minetest
.serialize({}))
919 meta
:set_string("slices", minetest
.serialize({}))
921 local node
= minetest
.get_node(pos
)
922 local dir
= minetest
.facedir_to_dir(node
.param2
)
924 meta
:set_int("x_size", 1)
925 meta
:set_int("y_size", 1)
926 meta
:set_int("z_size", 1)
928 -- Don't take item from itemstack
931 can_dig
= function(pos
, player
)
932 local name
= player
:get_player_name()
933 local meta
= minetest
.get_meta(pos
)
934 if meta
:get_string("owner") == name
or
935 minetest
.check_player_privs(player
, "schematic_override") == true then
941 on_rightclick
= function(pos
, node
, player
)
942 local meta
= minetest
.get_meta(pos
)
943 local name
= player
:get_player_name()
944 if meta
:get_string("owner") == name
or
945 minetest
.check_player_privs(player
, "schematic_override") == true then
946 -- Get player attribute
947 local tab
= player
:get_attribute("schemedit:tab")
948 if not forms
[tab
] or not tab
then
952 schemedit
.show_formspec(pos
, player
, tab
, true)
955 after_destruct
= function(pos
)
956 schemedit
.unmark(pos
)
959 -- No support for Minetest Game's screwdriver
963 minetest
.register_tool("schemedit:probtool", {
964 description
= S("Schematic Node Probability Tool"),
965 _doc_items_longdesc
=
966 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"..
967 S("It allows you to set two things:").."\n"..
968 S("1) Set probability: Chance for any particular node to be actually placed (default: always placed)").."\n"..
969 S("2) Enable force placement: These nodes replace node other than air and ignore when placed in a schematic (default: off)"),
970 _doc_items_usagehelp
= "\n"..
971 S("BASIC USAGE:").."\n"..
972 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"..
973 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"..
974 S("NODE HUD:").."\n"..
975 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"..
976 S("To disable the node HUD, unselect the tool or hit “place” while not pointing anything.").."\n\n"..
977 S("UPDATING THE NODE HUD:").."\n"..
978 S("The node HUD is not updated automatically and 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."),
979 wield_image
= "schemedit_probtool.png",
980 inventory_image
= "schemedit_probtool.png",
981 liquids_pointable
= true,
982 groups
= { disable_repair
= 1 },
983 on_use
= function(itemstack
, user
, pointed_thing
)
984 local ctrl
= user
:get_player_control()
986 if not ctrl
.sneak
then
987 -- Open dialog to change the probability to apply to nodes
988 schemedit
.show_formspec(user
:get_pos(), user
, "probtool", true)
992 -- Display the probability and force_place values for nodes.
994 -- If a schematic creator was punched, only enable display for all nodes
995 -- within the creator's region.
996 local use_creator_region
= false
997 if pointed_thing
and pointed_thing
.type == "node" and pointed_thing
.under
then
998 punchpos
= pointed_thing
.under
999 local node
= minetest
.get_node(punchpos
)
1000 if node
.name
== "schemedit:creator" then
1001 local pos1
, pos2
= schemedit
.size(punchpos
)
1002 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
1003 schemedit
.display_node_probs_region(user
, pos1
, pos2
)
1008 -- Otherwise, just display the region close to the player
1009 schemedit
.display_node_probs_region(user
)
1012 on_secondary_use
= function(itemstack
, user
, pointed_thing
)
1013 schemedit
.clear_displayed_node_probs(user
)
1015 -- Set note probability and force_place and enable node probability display
1016 on_place
= function(itemstack
, placer
, pointed_thing
)
1017 -- Use pointed node's on_rightclick function first, if present
1018 local node
= minetest
.get_node(pointed_thing
.under
)
1019 if placer
and not placer
:get_player_control().sneak
then
1020 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
1021 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
1025 -- This sets the node probability of pointed node to the
1026 -- currently used probability stored in the tool.
1027 local pos
= pointed_thing
.under
1028 local node
= minetest
.get_node(pos
)
1029 -- Schematic void are ignored, they always have probability 0
1030 if node
.name
== "schemedit:void" then
1033 local nmeta
= minetest
.get_meta(pos
)
1034 local imeta
= itemstack
:get_meta()
1035 local prob
= tonumber(imeta
:get_string("schemedit_prob"))
1036 local force_place
= imeta
:get_string("schemedit_force_place")
1038 if not prob
or prob
== 255 then
1039 nmeta
:set_string("schemedit_prob", nil)
1041 nmeta
:set_string("schemedit_prob", prob
)
1043 if force_place
== "true" then
1044 nmeta
:set_string("schemedit_force_place", "true")
1046 nmeta
:set_string("schemedit_force_place", nil)
1049 -- Enable node probablity display
1050 schemedit
.display_node_probs_region(placer
)
1056 minetest
.register_node("schemedit:void", {
1057 description
= S("Schematic Void"),
1058 _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."),
1059 _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."),
1060 tiles
= { "schemedit_void.png" },
1061 drawtype
= "nodebox",
1062 is_ground_content
= false,
1063 paramtype
= "light",
1065 sunlight_propagates
= true,
1069 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
1072 groups
= { dig_immediate
= 3},
1075 -- [entity] Visible schematic border
1076 minetest
.register_entity("schemedit:display", {
1077 visual
= "upright_sprite",
1078 textures
= {"schemedit_border.png"},
1079 visual_size
= {x
=10, y
=10},
1082 static_save
= false,
1083 glow
= minetest
.LIGHT_MAX
,
1085 on_step
= function(self
, dtime
)
1087 self
.object
:remove()
1088 elseif not schemedit
.markers
[self
.id
] then
1089 self
.object
:remove()
1092 on_activate
= function(self
)
1093 self
.object
:set_armor_groups({immortal
= 1})
1097 minetest
.register_lbm({
1098 label
= "Reset schematic creator border entities",
1099 name
= "schemedit:reset_border",
1100 nodenames
= "schemedit:creator",
1101 run_at_every_load
= true,
1102 action
= function(pos
, node
)
1103 local meta
= minetest
.get_meta(pos
)
1104 meta
:set_string("schem_border", "false")
1108 -- [chatcommand] Place schematic
1109 minetest
.register_chatcommand("placeschem", {
1110 description
= S("Place schematic at the position specified or the current player position (loaded from @1)", export_path_trunc
),
1111 privs
= {debug
= true},
1112 params
= S("<schematic name>[.mts] [<x> <y> <z>]"),
1113 func
= function(name
, param
)
1114 local schem
, p
= string.match(param
, "^([^ ]+) *(.*)$")
1115 local pos
= minetest
.string_to_pos(p
)
1118 return false, S("No schematic file specified.")
1122 pos
= minetest
.get_player_by_name(name
):get_pos()
1125 -- Automatically add file name suffix if omitted
1127 if string.sub(schem
, string.len(schem
)-3, string.len(schem
)) == ".mts" then
1130 schem_full
= schem
.. ".mts"
1133 local success
= false
1134 local schem_path
= export_path_full
.. DIR_DELIM
.. schem_full
1135 if minetest
.read_schematic
then
1136 -- We don't call minetest.place_schematic with the path name directly because
1137 -- this would trigger the caching and we wouldn't get any updates to the schematic
1138 -- files when we reload. minetest.read_schematic circumvents that.
1139 local schematic
= minetest
.read_schematic(schem_path
, {})
1141 success
= minetest
.place_schematic(pos
, schematic
, "random", nil, false)
1144 -- Legacy support for Minetest versions that do not have minetest.read_schematic
1145 success
= minetest
.place_schematic(schem_path
, schematic
, "random", nil, false)
1148 if success
== nil then
1149 return false, S("Schematic file could not be loaded!")