1 local S
= minetest
.get_translator("schemedit")
2 local F
= minetest
.formspec_escape
8 local export_path_full
= table.concat({minetest
.get_worldpath(), "schems"}, DIR_DELIM
)
10 -- truncated export path so the server directory structure is not exposed publicly
11 local export_path_trunc
= table.concat({S("<world path>"), "schems"}, DIR_DELIM
)
13 local text_color
= "#D79E9E"
14 local text_color_number
= 0xD79E9E
16 local can_import
= minetest
.read_schematic
~= nil
18 schemedit
.markers
= {}
20 -- [local function] Renumber table
21 local function renumber(t
)
23 for _
, i
in pairs(t
) do
30 local export_schematic_to_lua
32 export_schematic_to_lua
= function(schematic
, filepath
, options
)
33 if not options
then options
= {} end
34 local str
= minetest
.serialize_schematic(schematic
, "lua", options
)
35 local file
= io
.open(filepath
, "w")
55 local displayed_waypoints
= {}
57 -- Sadly, the probabilities presented in Lua (0-255) are not identical to the REAL probabilities in the
58 -- schematic file (0-127). There are two converter functions to convert from one probability type to another.
59 -- This mod tries to retain the “Lua probability” as long as possible and only switches to “schematic probability”
60 -- on an actual export to a schematic.
62 function schemedit
.lua_prob_to_schematic_prob(lua_prob
)
63 return math
.floor(lua_prob
/ 2)
66 function schemedit
.schematic_prob_to_lua_prob(schematic_prob
)
67 return schematic_prob
* 2
71 -- [function] Add form
72 function schemedit
.add_form(name
, def
)
77 tabs
[#tabs
+ 1] = name
81 -- [function] Generate tabs
82 function schemedit
.generate_tabs(current
)
83 local retval
= "tabheader[0,0;tabs;"
84 for _
, t
in pairs(tabs
) do
86 if f
.tab
~= false and f
.caption
then
87 retval
= retval
..f
.caption
..","
89 if type(current
) ~= "number" and current
== f
.name
then
94 retval
= retval
:sub(1, -2) -- Strip last comma
95 retval
= retval
..";"..current
.."]" -- Close tabheader
99 -- [function] Handle tabs
100 function schemedit
.handle_tabs(pos
, name
, fields
)
101 local tab
= tonumber(fields
.tabs
)
102 if tab
and tabs
[tab
] and forms
[tabs
[tab]]
then
103 schemedit
.show_formspec(pos
, name
, forms
[tabs
[tab]]
.name
)
108 -- [function] Show formspec
109 function schemedit
.show_formspec(pos
, player
, tab
, show
, ...)
111 if type(player
) == "string" then
112 player
= minetest
.get_player_by_name(player
)
114 local name
= player
:get_player_name()
116 if show
~= false then
117 if not form_data
[name
] then
121 local form
= forms
[tab
].get(form_data
[name
], pos
, name
, ...)
122 if forms
[tab
].tab
then
123 form
= form
..schemedit
.generate_tabs(tab
)
126 minetest
.show_formspec(name
, "schemedit:"..tab
, form
)
129 -- Update player attribute
130 if forms
[tab
].cache_name
~= false then
131 player
:set_attribute("schemedit:tab", tab
)
134 minetest
.close_formspec(pname
, "schemedit:"..tab
)
139 -- [event] On receive fields
140 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
141 local formname
= formname
:split(":")
143 if formname
[1] == "schemedit" and forms
[formname
[2]]
then
144 local handle
= forms
[formname
[2]]
.handle
145 local name
= player
:get_player_name()
146 if contexts
[name
] then
147 if not form_data
[name
] then
151 if not schemedit
.handle_tabs(contexts
[name
], name
, fields
) and handle
then
152 handle(form_data
[name
], contexts
[name
], name
, fields
)
158 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
159 schemedit
.scan_metadata
= function(pos1
, pos2
)
162 for x
=pos1
.x
, pos2
.x
do
163 for y
=pos1
.y
, pos2
.y
do
164 for z
=pos1
.z
, pos2
.z
do
165 local scanpos
= {x
=x
, y
=y
, z
=z
}
166 local node
= minetest
.get_node_or_nil(scanpos
)
168 local prob
, force_place
169 if node
== nil or node
.name
== "schemedit:void" then
173 local meta
= minetest
.get_meta(scanpos
)
175 prob
= tonumber(meta
:get_string("schemedit_prob")) or 255
176 local fp
= meta
:get_string("schemedit_force_place")
184 local hashpos
= minetest
.hash_node_position(scanpos
)
185 prob_list
[hashpos
] = {
188 force_place
= force_place
,
197 -- Sets probability and force_place metadata of an item.
198 -- Also updates item description.
199 -- The itemstack is updated in-place.
200 local function set_item_metadata(itemstack
, prob
, force_place
)
201 local smeta
= itemstack
:get_meta()
202 local prob_desc
= "\n"..S("Probability: @1", prob
or
203 smeta
:get_string("schemedit_prob") or S("Not Set"))
204 -- Update probability
205 if prob
and prob
>= 0 and prob
< 255 then
206 smeta
:set_string("schemedit_prob", tostring(prob
))
207 elseif prob
and prob
== 255 then
208 -- Clear prob metadata for default probability
210 smeta
:set_string("schemedit_prob", nil)
212 prob_desc
= "\n"..S("Probability: @1", smeta
:get_string("schemedit_prob") or
216 -- Update force place
217 if force_place
== true then
218 smeta
:set_string("schemedit_force_place", "true")
219 elseif force_place
== false then
220 smeta
:set_string("schemedit_force_place", nil)
223 -- Update description
224 local desc
= minetest
.registered_items
[itemstack
:get_name()].description
225 local meta_desc
= smeta
:get_string("description")
226 if meta_desc
and meta_desc
~= "" then
230 local original_desc
= smeta
:get_string("original_description")
231 if original_desc
and original_desc
~= "" then
234 smeta
:set_string("original_description", desc
)
237 local force_desc
= ""
238 if smeta
:get_string("schemedit_force_place") == "true" then
239 force_desc
= "\n"..S("Force placement")
242 desc
= desc
..minetest
.colorize(text_color
, prob_desc
..force_desc
)
244 smeta
:set_string("description", desc
)
252 local import_btn
= ""
254 import_btn
= "button[0.5,2.5;6,1;import;"..F(S("Import schematic")).."]"
256 schemedit
.add_form("main", {
259 get
= function(self
, pos
, name
)
260 local meta
= minetest
.get_meta(pos
):to_table().fields
261 local strpos
= minetest
.pos_to_string(pos
)
262 local hashpos
= minetest
.hash_node_position(pos
)
265 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
266 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Hide border")).."]"
268 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Show border")).."]"
271 local xs
, ys
, zs
= meta
.x_size
or 1, meta
.y_size
or 1, meta
.z_size
or 1
272 local size
= {x
=xs
, y
=ys
, z
=zs
}
276 label[0.5,-0.1;]]..F(S("Position: @1", strpos
))..[[]
277 label[3,-0.1;]]..F(S("Owner: @1", name
))..[[]
278 label[0.5,0.4;]]..F(S("Schematic name: @1", meta
.schem_name
))..[[]
279 label[0.5,0.9;]]..F(S("Size: @1", minetest
.pos_to_string(size
)))..[[]
281 field[0.8,2;5,1;name;]]..F(S("Schematic name:"))..[[;]]..F(meta
.schem_name
or "")..[[]
282 button[5.3,1.69;1.2,1;save_name;]]..F(S("OK"))..[[]
283 tooltip[save_name;]]..F(S("Save schematic name"))..[[]
284 field_close_on_enter[name;false]
286 button[0.5,3.5;6,1;export;]]..F(S("Export schematic")).."]"..
288 textarea[0.8,4.5;6.2,5;;]]..F(S("Export/import path:\n@1",
289 export_path_trunc
.. DIR_DELIM
.. F(S("<name>"))..".mts"))..[[;]
290 field[0.8,7;2,1;x;]]..F(S("X size:"))..[[;]]..xs
..[[]
291 field[2.8,7;2,1;y;]]..F(S("Y size:"))..[[;]]..ys
..[[]
292 field[4.8,7;2,1;z;]]..F(S("Z size:"))..[[;]]..zs
..[[]
293 field_close_on_enter[x;false]
294 field_close_on_enter[y;false]
295 field_close_on_enter[z;false]
297 button[0.5,7.5;3,1;save;]]..F(S("Save size"))..[[]
300 if minetest
.get_modpath("doc") then
301 form
= form
.. "image_button[6.4,-0.2;0.8,0.8;doc_button_icon_lores.png;doc;]" ..
302 "tooltip[doc;"..F(S("Help")).."]"
306 handle
= function(self
, pos
, name
, fields
)
307 local realmeta
= minetest
.get_meta(pos
)
308 local meta
= realmeta
:to_table().fields
309 local hashpos
= minetest
.hash_node_position(pos
)
311 -- Save size vector values
312 if (fields
.x
and fields
.x
~= "") then
313 local x
= tonumber(fields
.x
)
315 meta
.x_size
= math
.max(x
, 1)
318 if (fields
.y
and fields
.y
~= "") then
319 local y
= tonumber(fields
.y
)
321 meta
.y_size
= math
.max(y
, 1)
324 if (fields
.z
and fields
.z
~= "") then
325 local z
= tonumber(fields
.z
)
327 meta
.z_size
= math
.max(z
, 1)
331 -- Save schematic name
333 meta
.schem_name
= fields
.name
337 doc
.show_entry(name
, "nodes", "schemedit:creator", true)
342 if fields
.border
then
343 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
344 schemedit
.unmark(pos
)
345 meta
.schem_border
= "false"
348 meta
.schem_border
= "true"
353 if fields
.export
and meta
.schem_name
and meta
.schem_name
~= "" then
354 local pos1
, pos2
= schemedit
.size(pos
)
355 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
356 local path
= export_path_full
.. DIR_DELIM
359 local plist
= schemedit
.scan_metadata(pos1
, pos2
)
360 local probability_list
= {}
361 for hash
, i
in pairs(plist
) do
362 local prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
)
363 if i
.force_place
== true then
367 table.insert(probability_list
, {
368 pos
= minetest
.get_position_from_hash(hash
),
373 local slist
= minetest
.deserialize(meta
.slices
)
374 local slice_list
= {}
375 for _
, i
in pairs(slist
) do
376 slice_list
[#slice_list
+ 1] = {
377 ypos
= pos
.y
+ i
.ypos
,
378 prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
),
382 local filepath
= path
..meta
.schem_name
..".mts"
383 local res
= minetest
.create_schematic(pos1
, pos2
, probability_list
, filepath
, slice_list
)
386 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
387 S("Exported schematic to @1", filepath
)))
388 -- Additional export to Lua file if MTS export was successful
389 local schematic
= minetest
.read_schematic(filepath
, {})
390 if schematic
and minetest
.settings
:get_bool("schemedit_export_lua") then
391 local filepath_lua
= path
..meta
.schem_name
..".lua"
392 res
= export_schematic_to_lua(schematic
, filepath_lua
)
394 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
395 S("Exported schematic to @1", filepath_lua
)))
399 minetest
.chat_send_player(name
, minetest
.colorize("red",
400 S("Failed to export schematic to @1", filepath
)))
405 if fields
.import
and meta
.schem_name
and meta
.schem_name
~= "" then
406 if not minetest
.get_player_privs(name
).debug
then
407 minetest
.chat_send_player(name
, minetest
.colorize("red",
408 S("Insufficient privileges! You need the “debug” privilege to do this.")))
412 if not can_import
then
416 local node
= minetest
.get_node(pos
)
417 local path
= export_path_full
.. DIR_DELIM
419 local filepath
= path
..meta
.schem_name
..".mts"
420 local schematic
= minetest
.read_schematic(filepath
, {write_yslice_prob
="low"})
421 local success
= false
424 meta
.x_size
= schematic
.size
.x
425 meta
.y_size
= schematic
.size
.y
426 meta
.z_size
= schematic
.size
.z
427 meta
.slices
= minetest
.serialize(schematic
.yslice_prob
)
429 if node
.param2
== 1 then
430 pos1
= vector
.add(pos
, {x
=1,y
=0,z
=-meta
.z_size
+1})
431 elseif node
.param2
== 2 then
432 pos1
= vector
.add(pos
, {x
=-meta
.x_size
+1,y
=0,z
=-meta
.z_size
})
433 elseif node
.param2
== 3 then
434 pos1
= vector
.add(pos
, {x
=-meta
.x_size
,y
=0,z
=0})
436 pos1
= vector
.add(pos
, {x
=0,y
=0,z
=1})
439 local schematic_for_meta
= table.copy(schematic
)
440 -- Strip probability data for placement
441 schematic
.yslice_prob
= {}
442 for d
=1, #schematic
.data
do
443 schematic
.data
[d
].prob
= nil
447 success
= minetest
.place_schematic(pos1
, schematic
, "0", nil, true)
449 -- Add special schematic data to nodes
452 for z
=0, meta
.z_size
-1 do
453 for y
=0, meta
.y_size
-1 do
454 for x
=0, meta
.x_size
-1 do
455 local data
= schematic_for_meta
.data
[d
]
456 local pp
= {x
=pos1
.x
+x
, y
=pos1
.y
+y
, z
=pos1
.z
+z
}
457 if data
.prob
== 0 then
458 minetest
.set_node(pp
, {name
="schemedit:void"})
460 local meta
= minetest
.get_meta(pp
)
461 if data
.prob
and data
.prob
~= 255 and data
.prob
~= 254 then
462 meta
:set_string("schemedit_prob", tostring(data
.prob
))
464 meta
:set_string("schemedit_prob", "")
466 if data
.force_place
then
467 meta
:set_string("schemedit_force_place", "true")
469 meta
:set_string("schemedit_force_place", "")
479 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
480 S("Imported schematic from @1", filepath
)))
482 minetest
.chat_send_player(name
, minetest
.colorize("red",
483 S("Failed to import schematic from @1", filepath
)))
489 -- Save meta before updating visuals
490 local inv
= realmeta
:get_inventory():get_lists()
491 realmeta
:from_table({fields
= meta
, inventory
= inv
})
494 if not fields
.border
and meta
.schem_border
== "true" then
499 if not fields
.quit
then
500 schemedit
.show_formspec(pos
, minetest
.get_player_by_name(name
), "main")
505 schemedit
.add_form("slice", {
506 caption
= S("Y Slices"),
508 get
= function(self
, pos
, name
, visible_panel
)
509 local meta
= minetest
.get_meta(pos
):to_table().fields
511 self
.selected
= self
.selected
or 1
512 local selected
= tostring(self
.selected
)
513 local slice_list
= minetest
.deserialize(meta
.slices
)
515 for _
, i
in pairs(slice_list
) do
516 local insert
= F(S("Y = @1; Probability = @2", tostring(i
.ypos
), tostring(i
.prob
)))
517 slices
= slices
..insert
..","
519 slices
= slices
:sub(1, -2) -- Remove final comma
523 table[0,0;6.8,6;slices;]]..slices
..[[;]]..selected
..[[]
526 if self
.panel_add
or self
.panel_edit
then
527 local ypos_default
, prob_default
= "", ""
528 local done_button
= "button[5,7.18;2,1;done_add;"..F(S("Done")).."]"
529 if self
.panel_edit
then
530 done_button
= "button[5,7.18;2,1;done_edit;"..F(S("Done")).."]"
531 if slice_list
[self
.selected
] then
532 ypos_default
= slice_list
[self
.selected
].ypos
533 prob_default
= slice_list
[self
.selected
].prob
538 field[0.3,7.5;2.5,1;ypos;]]..F(S("Y position (max. @1):", (meta
.y_size
- 1)))..[[;]]..ypos_default
..[[]
539 field[2.8,7.5;2.5,1;prob;]]..F(S("Probability (0-255):"))..[[;]]..prob_default
..[[]
540 field_close_on_enter[ypos;false]
541 field_close_on_enter[prob;false]
545 if not self
.panel_edit
then
546 form
= form
.."button[0,6;2.4,1;add;"..F(S("+ Add slice")).."]"
549 if slices
~= "" and self
.selected
and not self
.panel_add
then
550 if not self
.panel_edit
then
552 button[2.4,6;2.4,1;remove;]]..F(S("- Remove slice"))..[[]
553 button[4.8,6;2.4,1;edit;]]..F(S("+/- Edit slice"))..[[]
557 button[2.4,6;2.4,1;remove;]]..F(S("- Remove slice"))..[[]
558 button[4.8,6;2.4,1;edit;]]..F(S("+/- Edit slice"))..[[]
565 handle
= function(self
, pos
, name
, fields
)
566 local meta
= minetest
.get_meta(pos
)
567 local player
= minetest
.get_player_by_name(name
)
569 if fields
.slices
then
570 local slices
= fields
.slices
:split(":")
571 self
.selected
= tonumber(slices
[2])
575 if not self
.panel_add
then
576 self
.panel_add
= true
577 schemedit
.show_formspec(pos
, player
, "slice")
580 schemedit
.show_formspec(pos
, player
, "slice")
584 local ypos
, prob
= tonumber(fields
.ypos
), tonumber(fields
.prob
)
585 if (fields
.done_add
or fields
.done_edit
) and fields
.ypos
and fields
.prob
and
586 fields
.ypos
~= "" and fields
.prob
~= "" and ypos
and prob
and
587 ypos
<= (meta
:get_int("y_size") - 1) and prob
>= 0 and prob
<= 255 then
588 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
589 local index
= #slice_list
+ 1
590 if fields
.done_edit
then
591 index
= self
.selected
594 slice_list
[index
] = {ypos
= ypos
, prob
= prob
}
596 meta
:set_string("slices", minetest
.serialize(slice_list
))
598 -- Update and show formspec
600 schemedit
.show_formspec(pos
, player
, "slice")
603 if fields
.remove and self
.selected
then
604 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
605 slice_list
[self
.selected
] = nil
606 meta
:set_string("slices", minetest
.serialize(renumber(slice_list
)))
610 self
.panel_edit
= nil
611 schemedit
.show_formspec(pos
, player
, "slice")
615 if not self
.panel_edit
then
616 self
.panel_edit
= true
617 schemedit
.show_formspec(pos
, player
, "slice")
619 self
.panel_edit
= nil
620 schemedit
.show_formspec(pos
, player
, "slice")
626 schemedit
.add_form("probtool", {
628 caption
= S("Schematic Node Probability Tool"),
629 get
= function(self
, pos
, name
)
630 local player
= minetest
.get_player_by_name(name
)
634 local probtool
= player
:get_wielded_item()
635 if probtool
:get_name() ~= "schemedit:probtool" then
639 local meta
= probtool
:get_meta()
640 local prob
= tonumber(meta
:get_string("schemedit_prob"))
641 local force_place
= meta
:get_string("schemedit_force_place")
646 if force_place
== nil or force_place
== "" then
647 force_place
= "false"
649 local form
= "size[5,4]"..
650 "label[0,0;"..F(S("Schematic Node Probability Tool")).."]"..
651 "field[0.75,1;4,1;prob;"..F(S("Probability (0-255)"))..";"..prob
.."]"..
652 "checkbox[0.60,1.5;force_place;"..F(S("Force placement"))..";" .. force_place
.. "]" ..
653 "button_exit[0.25,3;2,1;cancel;"..F(S("Cancel")).."]"..
654 "button_exit[2.75,3;2,1;submit;"..F(S("Apply")).."]"..
655 "tooltip[prob;"..F(S("Probability that the node will be placed")).."]"..
656 "tooltip[force_place;"..F(S("If enabled, the node will replace nodes other than air and ignore")).."]"..
657 "field_close_on_enter[prob;false]"
660 handle
= function(self
, pos
, name
, fields
)
661 if fields
.submit
then
662 local prob
= tonumber(fields
.prob
)
664 local player
= minetest
.get_player_by_name(name
)
668 local probtool
= player
:get_wielded_item()
669 if probtool
:get_name() ~= "schemedit:probtool" then
673 local force_place
= self
.force_place
== true
675 set_item_metadata(probtool
, prob
, force_place
)
677 -- Repurpose the tool's wear bar to display the set probability
678 probtool
:set_wear(math
.floor(((255-prob
)/255)*65535))
680 player
:set_wielded_item(probtool
)
683 if fields
.force_place
== "true" then
684 self
.force_place
= true
685 elseif fields
.force_place
== "false" then
686 self
.force_place
= false
695 --- Copies and modifies positions `pos1` and `pos2` so that each component of
696 -- `pos1` is less than or equal to the corresponding component of `pos2`.
697 -- Returns the new positions.
698 function schemedit
.sort_pos(pos1
, pos2
)
699 if not pos1
or not pos2
then
703 pos1
, pos2
= table.copy(pos1
), table.copy(pos2
)
704 if pos1
.x
> pos2
.x
then
705 pos2
.x
, pos1
.x
= pos1
.x
, pos2
.x
707 if pos1
.y
> pos2
.y
then
708 pos2
.y
, pos1
.y
= pos1
.y
, pos2
.y
710 if pos1
.z
> pos2
.z
then
711 pos2
.z
, pos1
.z
= pos1
.z
, pos2
.z
716 -- [function] Prepare size
717 function schemedit
.size(pos
)
718 local pos1
= vector
.new(pos
)
719 local meta
= minetest
.get_meta(pos
)
720 local node
= minetest
.get_node(pos
)
721 local param2
= node
.param2
723 x
= meta
:get_int("x_size"),
724 y
= math
.max(meta
:get_int("y_size") - 1, 0),
725 z
= meta
:get_int("z_size"),
729 local new_pos
= vector
.add({x
= size
.z
, y
= size
.y
, z
= -size
.x
}, pos
)
731 new_pos
.z
= new_pos
.z
+ 1
733 elseif param2
== 2 then
734 local new_pos
= vector
.add({x
= -size
.x
, y
= size
.y
, z
= -size
.z
}, pos
)
736 new_pos
.x
= new_pos
.x
+ 1
738 elseif param2
== 3 then
739 local new_pos
= vector
.add({x
= -size
.z
, y
= size
.y
, z
= size
.x
}, pos
)
741 new_pos
.z
= new_pos
.z
- 1
744 local new_pos
= vector
.add(size
, pos
)
746 new_pos
.x
= new_pos
.x
- 1
751 -- [function] Mark region
752 function schemedit
.mark(pos
)
753 schemedit
.unmark(pos
)
755 local id
= minetest
.hash_node_position(pos
)
756 local owner
= minetest
.get_meta(pos
):get_string("owner")
757 local pos1
, pos2
= schemedit
.size(pos
)
758 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
760 local thickness
= 0.2
761 local sizex
, sizey
, sizez
= (1 + pos2
.x
- pos1
.x
) / 2, (1 + pos2
.y
- pos1
.y
) / 2, (1 + pos2
.z
- pos1
.z
) / 2
767 for _
, z
in ipairs({pos1
.z
- 0.5, pos2
.z
+ 0.5}) do
773 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= pos1
.y
+ sizey
- 0.5, z
= z
+ offset
}, "schemedit:display")
774 if marker
~= nil then
775 marker
:set_properties({
776 visual_size
={x
=(sizex
+0.01) * 2, y
=(sizey
+0.01) * 2},
778 marker
:get_luaentity().id
= id
779 marker
:get_luaentity().owner
= owner
780 table.insert(m
, marker
)
787 for _
, x
in ipairs({pos1
.x
- 0.5, pos2
.x
+ 0.5}) do
794 local marker
= minetest
.add_entity({x
= x
+ offset
, y
= pos1
.y
+ sizey
- 0.5, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
795 if marker
~= nil then
796 marker
:set_properties({
797 visual_size
={x
=(sizez
+0.01) * 2, y
=(sizey
+0.01) * 2},
799 marker
:set_rotation({x
=0, y
=math
.pi
/ 2, z
=0})
800 marker
:get_luaentity().id
= id
801 marker
:get_luaentity().owner
= owner
802 table.insert(m
, marker
)
809 for _
, y
in ipairs({pos1
.y
- 0.5, pos2
.y
+ 0.5}) do
816 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= y
+ offset
, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
817 if marker
~= nil then
818 marker
:set_properties({
819 visual_size
={x
=(sizex
+0.01) * 2, y
=(sizez
+0.01) * 2},
821 marker
:set_rotation({x
=math
.pi
/2, y
=0, z
=0})
822 marker
:get_luaentity().id
= id
823 marker
:get_luaentity().owner
= owner
824 table.insert(m
, marker
)
831 schemedit
.markers
[id
] = m
835 -- [function] Unmark region
836 function schemedit
.unmark(pos
)
837 local id
= minetest
.hash_node_position(pos
)
838 if schemedit
.markers
[id
] then
840 for _
, entity
in ipairs(schemedit
.markers
[id
]) do
849 --- Mark node probability values near player
852 -- Show probability and force_place status of a particular position for player in HUD.
853 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
854 -- The distance to the node is also displayed below that. This can't be avoided and is
855 -- and artifact of the waypoint HUD element.
856 function schemedit
.display_node_prob(player
, pos
, prob
, force_place
)
858 if prob
and force_place
== true then
859 wpstring
= string.format("%s [F]", prob
)
860 elseif prob
and type(tonumber(prob
)) == "number" then
862 elseif force_place
== true then
866 return player
:hud_add({
867 hud_elem_type
= "waypoint",
870 text
= "m", -- For the distance artifact
871 number = text_color_number
,
877 -- Display the node probabilities and force_place status of the nodes in a region.
878 -- By default, this is done for nodes near the player (distance: 5).
879 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
880 function schemedit
.display_node_probs_region(player
, pos1
, pos2
)
881 local playername
= player
:get_player_name()
882 local pos
= vector
.round(player
:get_pos())
885 -- Default: 5 nodes away from player in any direction
887 pos1
= vector
.subtract(pos
, dist
)
890 pos2
= vector
.add(pos
, dist
)
892 for x
=pos1
.x
, pos2
.x
do
893 for y
=pos1
.y
, pos2
.y
do
894 for z
=pos1
.z
, pos2
.z
do
895 local checkpos
= {x
=x
, y
=y
, z
=z
}
896 local nodehash
= minetest
.hash_node_position(checkpos
)
898 -- If node is already displayed, remove it so it can re replaced later
899 if displayed_waypoints
[playername
][nodehash
] then
900 player
:hud_remove(displayed_waypoints
[playername
][nodehash
])
901 displayed_waypoints
[playername
][nodehash
] = nil
904 local prob
, force_place
905 local meta
= minetest
.get_meta(checkpos
)
906 prob
= meta
:get_string("schemedit_prob")
907 force_place
= meta
:get_string("schemedit_force_place") == "true"
908 local hud_id
= schemedit
.display_node_prob(player
, checkpos
, prob
, force_place
)
910 displayed_waypoints
[playername
][nodehash
] = hud_id
911 displayed_waypoints
[playername
].display_active
= true
918 -- Remove all active displayed node statuses.
919 function schemedit
.clear_displayed_node_probs(player
)
920 local playername
= player
:get_player_name()
921 for nodehash
, hud_id
in pairs(displayed_waypoints
[playername
]) do
922 player
:hud_remove(hud_id
)
923 displayed_waypoints
[playername
][nodehash
] = nil
924 displayed_waypoints
[playername
].display_active
= false
928 minetest
.register_on_joinplayer(function(player
)
929 displayed_waypoints
[player
:get_player_name()] = {
930 display_active
= false -- If true, there *might* be at least one active node prob HUD display
931 -- If false, no node probabilities are displayed for sure.
935 minetest
.register_on_leaveplayer(function(player
)
936 displayed_waypoints
[player
:get_player_name()] = nil
939 -- Regularily clear the displayed node probabilities and force_place
940 -- for all players who do not wield the probtool.
941 -- This makes sure the screen is not spammed with information when it
944 minetest
.register_globalstep(function(dtime
)
945 cleartimer
= cleartimer
+ dtime
946 if cleartimer
> 2 then
947 local players
= minetest
.get_connected_players()
948 for p
= 1, #players
do
949 local player
= players
[p
]
950 local pname
= player
:get_player_name()
951 if displayed_waypoints
[pname
].display_active
then
952 local item
= player
:get_wielded_item()
953 if item
:get_name() ~= "schemedit:probtool" then
954 schemedit
.clear_displayed_node_probs(player
)
966 -- [priv] schematic_override
967 minetest
.register_privilege("schematic_override", {
968 description
= S("Allows you to access schemedit nodes not owned by you"),
969 give_to_singleplayer
= false,
972 local help_import
= ""
974 help_import
= S("Importing a schematic will load a schematic from the world directory, place it in front of the schematic creator and sets probability and force-place data accordingly.").."\n"
977 -- [node] Schematic creator
978 minetest
.register_node("schemedit:creator", {
979 description
= S("Schematic Creator"),
980 _doc_items_longdesc
= S("The schematic creator is used to save a region of the world into a schematic file (.mts)."),
981 _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"..
982 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"..
984 S("The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.").."\n\n"..
985 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"..
986 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."),
987 tiles
= {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
988 "schemedit_creator_sides.png"},
989 groups
= { dig_immediate
= 2},
990 paramtype2
= "facedir",
991 is_ground_content
= false,
993 after_place_node
= function(pos
, player
)
994 local name
= player
:get_player_name()
995 local meta
= minetest
.get_meta(pos
)
997 meta
:set_string("owner", name
)
998 meta
:set_string("infotext", S("Schematic Creator").."\n"..S("(owned by @1)", name
))
999 meta
:set_string("prob_list", minetest
.serialize({}))
1000 meta
:set_string("slices", minetest
.serialize({}))
1002 local node
= minetest
.get_node(pos
)
1003 local dir
= minetest
.facedir_to_dir(node
.param2
)
1005 meta
:set_int("x_size", 1)
1006 meta
:set_int("y_size", 1)
1007 meta
:set_int("z_size", 1)
1009 -- Don't take item from itemstack
1012 can_dig
= function(pos
, player
)
1013 local name
= player
:get_player_name()
1014 local meta
= minetest
.get_meta(pos
)
1015 if meta
:get_string("owner") == name
or
1016 minetest
.check_player_privs(player
, "schematic_override") == true then
1022 on_rightclick
= function(pos
, node
, player
)
1023 local meta
= minetest
.get_meta(pos
)
1024 local name
= player
:get_player_name()
1025 if meta
:get_string("owner") == name
or
1026 minetest
.check_player_privs(player
, "schematic_override") == true then
1027 -- Get player attribute
1028 local tab
= player
:get_attribute("schemedit:tab")
1029 if not forms
[tab
] or not tab
then
1033 schemedit
.show_formspec(pos
, player
, tab
, true)
1036 after_destruct
= function(pos
)
1037 schemedit
.unmark(pos
)
1040 -- No support for Minetest Game's screwdriver
1044 minetest
.register_tool("schemedit:probtool", {
1045 description
= S("Schematic Node Probability Tool"),
1046 _doc_items_longdesc
=
1047 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"..
1048 S("It allows you to set two things:").."\n"..
1049 S("1) Set probability: Chance for any particular node to be actually placed (default: always placed)").."\n"..
1050 S("2) Enable force placement: These nodes replace node other than air and ignore when placed in a schematic (default: off)"),
1051 _doc_items_usagehelp
= "\n"..
1052 S("BASIC USAGE:").."\n"..
1053 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"..
1054 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"..
1055 S("NODE HUD:").."\n"..
1056 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"..
1057 S("To disable the node HUD, unselect the tool or hit “place” while not pointing anything.").."\n\n"..
1058 S("UPDATING THE NODE HUD:").."\n"..
1059 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."),
1060 wield_image
= "schemedit_probtool.png",
1061 inventory_image
= "schemedit_probtool.png",
1062 liquids_pointable
= true,
1063 groups
= { disable_repair
= 1 },
1064 on_use
= function(itemstack
, user
, pointed_thing
)
1065 local ctrl
= user
:get_player_control()
1067 if not ctrl
.sneak
then
1068 -- Open dialog to change the probability to apply to nodes
1069 schemedit
.show_formspec(user
:get_pos(), user
, "probtool", true)
1073 -- Display the probability and force_place values for nodes.
1075 -- If a schematic creator was punched, only enable display for all nodes
1076 -- within the creator's region.
1077 local use_creator_region
= false
1078 if pointed_thing
and pointed_thing
.type == "node" and pointed_thing
.under
then
1079 local punchpos
= pointed_thing
.under
1080 local node
= minetest
.get_node(punchpos
)
1081 if node
.name
== "schemedit:creator" then
1082 local pos1
, pos2
= schemedit
.size(punchpos
)
1083 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
1084 schemedit
.display_node_probs_region(user
, pos1
, pos2
)
1089 -- Otherwise, just display the region close to the player
1090 schemedit
.display_node_probs_region(user
)
1093 on_secondary_use
= function(itemstack
, user
, pointed_thing
)
1094 schemedit
.clear_displayed_node_probs(user
)
1096 -- Set note probability and force_place and enable node probability display
1097 on_place
= function(itemstack
, placer
, pointed_thing
)
1098 -- Use pointed node's on_rightclick function first, if present
1099 local node
= minetest
.get_node(pointed_thing
.under
)
1100 if placer
and not placer
:get_player_control().sneak
then
1101 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
1102 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
1106 -- This sets the node probability of pointed node to the
1107 -- currently used probability stored in the tool.
1108 local pos
= pointed_thing
.under
1109 local node
= minetest
.get_node(pos
)
1110 -- Schematic void are ignored, they always have probability 0
1111 if node
.name
== "schemedit:void" then
1114 local nmeta
= minetest
.get_meta(pos
)
1115 local imeta
= itemstack
:get_meta()
1116 local prob
= tonumber(imeta
:get_string("schemedit_prob"))
1117 local force_place
= imeta
:get_string("schemedit_force_place")
1119 if not prob
or prob
== 255 then
1120 nmeta
:set_string("schemedit_prob", nil)
1122 nmeta
:set_string("schemedit_prob", prob
)
1124 if force_place
== "true" then
1125 nmeta
:set_string("schemedit_force_place", "true")
1127 nmeta
:set_string("schemedit_force_place", nil)
1130 -- Enable node probablity display
1131 schemedit
.display_node_probs_region(placer
)
1137 minetest
.register_node("schemedit:void", {
1138 description
= S("Schematic Void"),
1139 _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."),
1140 _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."),
1141 tiles
= { "schemedit_void.png" },
1142 drawtype
= "nodebox",
1143 is_ground_content
= false,
1144 paramtype
= "light",
1146 sunlight_propagates
= true,
1150 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
1153 groups
= { dig_immediate
= 3},
1156 -- [entity] Visible schematic border
1157 minetest
.register_entity("schemedit:display", {
1158 visual
= "upright_sprite",
1159 textures
= {"schemedit_border.png"},
1160 visual_size
= {x
=10, y
=10},
1163 static_save
= false,
1164 glow
= minetest
.LIGHT_MAX
,
1166 on_step
= function(self
, dtime
)
1168 self
.object
:remove()
1169 elseif not schemedit
.markers
[self
.id
] then
1170 self
.object
:remove()
1173 on_activate
= function(self
)
1174 self
.object
:set_armor_groups({immortal
= 1})
1178 minetest
.register_lbm({
1179 label
= "Reset schematic creator border entities",
1180 name
= "schemedit:reset_border",
1181 nodenames
= "schemedit:creator",
1182 run_at_every_load
= true,
1183 action
= function(pos
, node
)
1184 local meta
= minetest
.get_meta(pos
)
1185 meta
:set_string("schem_border", "false")
1189 local function add_suffix(schem
)
1190 -- Automatically add file name suffix if omitted
1191 local schem_full
, schem_lua
1192 if string.sub(schem
, string.len(schem
)-3, string.len(schem
)) == ".mts" then
1194 schem_lua
= string.sub(schem
, 1, -5) .. ".lua"
1196 schem_full
= schem
.. ".mts"
1197 schem_lua
= schem
.. ".lua"
1199 return schem_full
, schem_lua
1202 -- [chatcommand] Place schematic
1203 minetest
.register_chatcommand("placeschem", {
1204 description
= S("Place schematic at the position specified or the current player position (loaded from @1)", export_path_trunc
),
1205 privs
= {debug
= true},
1206 params
= S("<schematic name>[.mts] [<x> <y> <z>]"),
1207 func
= function(name
, param
)
1208 local schem
, p
= string.match(param
, "^([^ ]+) *(.*)$")
1209 local pos
= minetest
.string_to_pos(p
)
1212 return false, S("No schematic file specified.")
1216 pos
= minetest
.get_player_by_name(name
):get_pos()
1219 local schem_full
, schem_lua
= add_suffix(schem
)
1220 local success
= false
1221 local schem_path
= export_path_full
.. DIR_DELIM
.. schem_full
1222 if minetest
.read_schematic
then
1223 -- We don't call minetest.place_schematic with the path name directly because
1224 -- this would trigger the caching and we wouldn't get any updates to the schematic
1225 -- files when we reload. minetest.read_schematic circumvents that.
1226 local schematic
= minetest
.read_schematic(schem_path
, {})
1228 success
= minetest
.place_schematic(pos
, schematic
, "random", nil, false)
1231 -- Legacy support for Minetest versions that do not have minetest.read_schematic
1232 success
= minetest
.place_schematic(schem_path
, schematic
, "random", nil, false)
1235 if success
== nil then
1236 return false, S("Schematic file could not be loaded!")
1244 -- [chatcommand] Convert MTS schematic file to .lua file
1245 minetest
.register_chatcommand("mts2lua", {
1246 description
= S("Convert .mts schematic file to .lua file (loaded from @1)", export_path_trunc
),
1247 privs
= {debug
= true},
1248 params
= S("<schematic name>[.mts] [comments]"),
1249 func
= function(name
, param
)
1250 local schem
, comments_str
= string.match(param
, "^([^ ]+) *(.*)$")
1253 return false, S("No schematic file specified.")
1256 local comments
= comments_str
== "comments"
1258 -- Automatically add file name suffix if omitted
1259 local schem_full
, schem_lua
= add_suffix(schem
)
1260 local schem_path
= export_path_full
.. DIR_DELIM
.. schem_full
1261 local schematic
= minetest
.read_schematic(schem_path
, {})
1264 local str
= minetest
.serialize_schematic(schematic
, "lua", {lua_use_comments
=comments
})
1265 local lua_path
= export_path_full
.. DIR_DELIM
.. schem_lua
1266 local file
= io
.open(lua_path
, "w")
1267 if file
and str
then
1271 return true, S("Exported schematic to @1", lua_path
)
1273 return false, S("Failed!")