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
29 minetest
.register_privilege("schemedit")
30 local NEEDED_PRIV
= "schemedit"
31 local function check_priv(player_name
)
32 local privs
= minetest
.get_player_privs(player_name
)
33 if privs
[NEEDED_PRIV
] then
36 minetest
.chat_send_player(player_name
, minetest
.colorize("red",
37 S("Insufficient privileges! You need the “@1” privilege to use this.", NEEDED_PRIV
)))
43 local export_schematic_to_lua
45 export_schematic_to_lua
= function(schematic
, filepath
, options
)
46 if not options
then options
= {} end
47 local str
= minetest
.serialize_schematic(schematic
, "lua", options
)
48 local file
= io
.open(filepath
, "w")
68 local displayed_waypoints
= {}
70 -- Sadly, the probabilities presented in Lua (0-255) are not identical to the REAL probabilities in the
71 -- schematic file (0-127). There are two converter functions to convert from one probability type to another.
72 -- This mod tries to retain the “Lua probability” as long as possible and only switches to “schematic probability”
73 -- on an actual export to a schematic.
75 function schemedit
.lua_prob_to_schematic_prob(lua_prob
)
76 return math
.floor(lua_prob
/ 2)
79 function schemedit
.schematic_prob_to_lua_prob(schematic_prob
)
80 return schematic_prob
* 2
84 -- [function] Add form
85 function schemedit
.add_form(name
, def
)
90 tabs
[#tabs
+ 1] = name
94 -- [function] Generate tabs
95 function schemedit
.generate_tabs(current
)
96 local retval
= "tabheader[0,0;tabs;"
97 for _
, t
in pairs(tabs
) do
99 if f
.tab
~= false and f
.caption
then
100 retval
= retval
..f
.caption
..","
102 if type(current
) ~= "number" and current
== f
.name
then
107 retval
= retval
:sub(1, -2) -- Strip last comma
108 retval
= retval
..";"..current
.."]" -- Close tabheader
112 -- [function] Handle tabs
113 function schemedit
.handle_tabs(pos
, name
, fields
)
114 local tab
= tonumber(fields
.tabs
)
115 if tab
and tabs
[tab
] and forms
[tabs
[tab]]
then
116 schemedit
.show_formspec(pos
, name
, forms
[tabs
[tab]]
.name
)
121 -- [function] Show formspec
122 function schemedit
.show_formspec(pos
, player
, tab
, show
, ...)
124 if type(player
) == "string" then
125 player
= minetest
.get_player_by_name(player
)
127 local name
= player
:get_player_name()
129 if show
~= false then
130 if not form_data
[name
] then
134 local form
= forms
[tab
].get(form_data
[name
], pos
, name
, ...)
135 if forms
[tab
].tab
then
136 form
= form
..schemedit
.generate_tabs(tab
)
139 minetest
.show_formspec(name
, "schemedit:"..tab
, form
)
142 -- Update player attribute
143 if forms
[tab
].cache_name
~= false then
144 player
:set_attribute("schemedit:tab", tab
)
147 minetest
.close_formspec(pname
, "schemedit:"..tab
)
152 -- [event] On receive fields
153 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
154 local formname
= formname
:split(":")
156 if formname
[1] == "schemedit" and forms
[formname
[2]]
then
157 local handle
= forms
[formname
[2]]
.handle
158 local name
= player
:get_player_name()
159 if contexts
[name
] then
160 if not form_data
[name
] then
164 if not schemedit
.handle_tabs(contexts
[name
], name
, fields
) and handle
then
165 handle(form_data
[name
], contexts
[name
], name
, fields
)
171 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
172 schemedit
.scan_metadata
= function(pos1
, pos2
)
175 for x
=pos1
.x
, pos2
.x
do
176 for y
=pos1
.y
, pos2
.y
do
177 for z
=pos1
.z
, pos2
.z
do
178 local scanpos
= {x
=x
, y
=y
, z
=z
}
179 local node
= minetest
.get_node_or_nil(scanpos
)
181 local prob
, force_place
182 if node
== nil or node
.name
== "schemedit:void" then
186 local meta
= minetest
.get_meta(scanpos
)
188 prob
= tonumber(meta
:get_string("schemedit_prob")) or 255
189 local fp
= meta
:get_string("schemedit_force_place")
197 local hashpos
= minetest
.hash_node_position(scanpos
)
198 prob_list
[hashpos
] = {
201 force_place
= force_place
,
210 -- Sets probability and force_place metadata of an item.
211 -- Also updates item description.
212 -- The itemstack is updated in-place.
213 local function set_item_metadata(itemstack
, prob
, force_place
)
214 local smeta
= itemstack
:get_meta()
215 local prob_desc
= "\n"..S("Probability: @1", prob
or
216 smeta
:get_string("schemedit_prob") or S("Not Set"))
217 -- Update probability
218 if prob
and prob
>= 0 and prob
< 255 then
219 smeta
:set_string("schemedit_prob", tostring(prob
))
220 elseif prob
and prob
== 255 then
221 -- Clear prob metadata for default probability
223 smeta
:set_string("schemedit_prob", nil)
225 prob_desc
= "\n"..S("Probability: @1", smeta
:get_string("schemedit_prob") or
229 -- Update force place
230 if force_place
== true then
231 smeta
:set_string("schemedit_force_place", "true")
232 elseif force_place
== false then
233 smeta
:set_string("schemedit_force_place", nil)
236 -- Update description
237 local desc
= minetest
.registered_items
[itemstack
:get_name()].description
238 local meta_desc
= smeta
:get_string("description")
239 if meta_desc
and meta_desc
~= "" then
243 local original_desc
= smeta
:get_string("original_description")
244 if original_desc
and original_desc
~= "" then
247 smeta
:set_string("original_description", desc
)
250 local force_desc
= ""
251 if smeta
:get_string("schemedit_force_place") == "true" then
252 force_desc
= "\n"..S("Force placement")
255 desc
= desc
..minetest
.colorize(text_color
, prob_desc
..force_desc
)
257 smeta
:set_string("description", desc
)
265 local import_btn
= ""
267 import_btn
= "button[0.5,2.5;6,1;import;"..F(S("Import schematic")).."]"
269 schemedit
.add_form("main", {
272 get
= function(self
, pos
, name
)
273 local meta
= minetest
.get_meta(pos
):to_table().fields
274 local strpos
= minetest
.pos_to_string(pos
)
275 local hashpos
= minetest
.hash_node_position(pos
)
278 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
279 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Hide border")).."]"
281 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Show border")).."]"
284 local xs
, ys
, zs
= meta
.x_size
or 1, meta
.y_size
or 1, meta
.z_size
or 1
285 local size
= {x
=xs
, y
=ys
, z
=zs
}
289 label[0.5,-0.1;]]..F(S("Position: @1", strpos
))..[[]
290 label[3,-0.1;]]..F(S("Owner: @1", name
))..[[]
291 label[0.5,0.4;]]..F(S("Schematic name: @1", meta
.schem_name
))..[[]
292 label[0.5,0.9;]]..F(S("Size: @1", minetest
.pos_to_string(size
)))..[[]
294 field[0.8,2;5,1;name;]]..F(S("Schematic name:"))..[[;]]..F(meta
.schem_name
or "")..[[]
295 button[5.3,1.69;1.2,1;save_name;]]..F(S("OK"))..[[]
296 tooltip[save_name;]]..F(S("Save schematic name"))..[[]
297 field_close_on_enter[name;false]
299 button[0.5,3.5;6,1;export;]]..F(S("Export schematic")).."]"..
301 textarea[0.8,4.5;6.2,5;;]]..F(S("Export/import path:\n@1",
302 export_path_trunc
.. DIR_DELIM
.. F(S("<name>"))..".mts"))..[[;]
303 field[0.8,7;2,1;x;]]..F(S("X size:"))..[[;]]..xs
..[[]
304 field[2.8,7;2,1;y;]]..F(S("Y size:"))..[[;]]..ys
..[[]
305 field[4.8,7;2,1;z;]]..F(S("Z size:"))..[[;]]..zs
..[[]
306 field_close_on_enter[x;false]
307 field_close_on_enter[y;false]
308 field_close_on_enter[z;false]
310 button[0.5,7.5;3,1;save;]]..F(S("Save size"))..[[]
313 if minetest
.get_modpath("doc") then
314 form
= form
.. "image_button[6.4,-0.2;0.8,0.8;doc_button_icon_lores.png;doc;]" ..
315 "tooltip[doc;"..F(S("Help")).."]"
319 handle
= function(self
, pos
, name
, fields
)
320 if not check_priv(name
) then
324 local realmeta
= minetest
.get_meta(pos
)
325 local meta
= realmeta
:to_table().fields
326 local hashpos
= minetest
.hash_node_position(pos
)
328 -- Save size vector values
329 if (fields
.x
and fields
.x
~= "") then
330 local x
= tonumber(fields
.x
)
332 meta
.x_size
= math
.max(x
, 1)
335 if (fields
.y
and fields
.y
~= "") then
336 local y
= tonumber(fields
.y
)
338 meta
.y_size
= math
.max(y
, 1)
341 if (fields
.z
and fields
.z
~= "") then
342 local z
= tonumber(fields
.z
)
344 meta
.z_size
= math
.max(z
, 1)
348 -- Save schematic name
350 meta
.schem_name
= fields
.name
354 doc
.show_entry(name
, "nodes", "schemedit:creator", true)
359 if fields
.border
then
360 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
361 schemedit
.unmark(pos
)
362 meta
.schem_border
= "false"
365 meta
.schem_border
= "true"
370 if fields
.export
and meta
.schem_name
and meta
.schem_name
~= "" then
371 local pos1
, pos2
= schemedit
.size(pos
)
372 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
373 local path
= export_path_full
.. DIR_DELIM
376 local plist
= schemedit
.scan_metadata(pos1
, pos2
)
377 local probability_list
= {}
378 for hash
, i
in pairs(plist
) do
379 local prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
)
380 if i
.force_place
== true then
384 table.insert(probability_list
, {
385 pos
= minetest
.get_position_from_hash(hash
),
390 local slist
= minetest
.deserialize(meta
.slices
)
391 local slice_list
= {}
392 for _
, i
in pairs(slist
) do
393 slice_list
[#slice_list
+ 1] = {
394 ypos
= pos
.y
+ i
.ypos
,
395 prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
),
399 local filepath
= path
..meta
.schem_name
..".mts"
400 local res
= minetest
.create_schematic(pos1
, pos2
, probability_list
, filepath
, slice_list
)
403 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
404 S("Exported schematic to @1", filepath
)))
405 -- Additional export to Lua file if MTS export was successful
406 local schematic
= minetest
.read_schematic(filepath
, {})
407 if schematic
and minetest
.settings
:get_bool("schemedit_export_lua") then
408 local filepath_lua
= path
..meta
.schem_name
..".lua"
409 res
= export_schematic_to_lua(schematic
, filepath_lua
)
411 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
412 S("Exported schematic to @1", filepath_lua
)))
416 minetest
.chat_send_player(name
, minetest
.colorize("red",
417 S("Failed to export schematic to @1", filepath
)))
422 if fields
.import
and meta
.schem_name
and meta
.schem_name
~= "" then
423 if not can_import
then
427 local node
= minetest
.get_node(pos
)
428 local path
= export_path_full
.. DIR_DELIM
430 local filepath
= path
..meta
.schem_name
..".mts"
431 local schematic
= minetest
.read_schematic(filepath
, {write_yslice_prob
="low"})
432 local success
= false
435 meta
.x_size
= schematic
.size
.x
436 meta
.y_size
= schematic
.size
.y
437 meta
.z_size
= schematic
.size
.z
438 meta
.slices
= minetest
.serialize(schematic
.yslice_prob
)
440 if node
.param2
== 1 then
441 pos1
= vector
.add(pos
, {x
=1,y
=0,z
=-meta
.z_size
+1})
442 elseif node
.param2
== 2 then
443 pos1
= vector
.add(pos
, {x
=-meta
.x_size
+1,y
=0,z
=-meta
.z_size
})
444 elseif node
.param2
== 3 then
445 pos1
= vector
.add(pos
, {x
=-meta
.x_size
,y
=0,z
=0})
447 pos1
= vector
.add(pos
, {x
=0,y
=0,z
=1})
450 local schematic_for_meta
= table.copy(schematic
)
451 -- Strip probability data for placement
452 schematic
.yslice_prob
= {}
453 for d
=1, #schematic
.data
do
454 schematic
.data
[d
].prob
= nil
458 success
= minetest
.place_schematic(pos1
, schematic
, "0", nil, true)
460 -- Add special schematic data to nodes
463 for z
=0, meta
.z_size
-1 do
464 for y
=0, meta
.y_size
-1 do
465 for x
=0, meta
.x_size
-1 do
466 local data
= schematic_for_meta
.data
[d
]
467 local pp
= {x
=pos1
.x
+x
, y
=pos1
.y
+y
, z
=pos1
.z
+z
}
468 if data
.prob
== 0 then
469 minetest
.set_node(pp
, {name
="schemedit:void"})
471 local meta
= minetest
.get_meta(pp
)
472 if data
.prob
and data
.prob
~= 255 and data
.prob
~= 254 then
473 meta
:set_string("schemedit_prob", tostring(data
.prob
))
475 meta
:set_string("schemedit_prob", "")
477 if data
.force_place
then
478 meta
:set_string("schemedit_force_place", "true")
480 meta
:set_string("schemedit_force_place", "")
490 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
491 S("Imported schematic from @1", filepath
)))
493 minetest
.chat_send_player(name
, minetest
.colorize("red",
494 S("Failed to import schematic from @1", filepath
)))
500 -- Save meta before updating visuals
501 local inv
= realmeta
:get_inventory():get_lists()
502 realmeta
:from_table({fields
= meta
, inventory
= inv
})
505 if not fields
.border
and meta
.schem_border
== "true" then
510 if not fields
.quit
then
511 schemedit
.show_formspec(pos
, minetest
.get_player_by_name(name
), "main")
516 schemedit
.add_form("slice", {
517 caption
= S("Y Slices"),
519 get
= function(self
, pos
, name
, visible_panel
)
520 local meta
= minetest
.get_meta(pos
):to_table().fields
522 self
.selected
= self
.selected
or 1
523 local selected
= tostring(self
.selected
)
524 local slice_list
= minetest
.deserialize(meta
.slices
)
526 for _
, i
in pairs(slice_list
) do
527 local insert
= F(S("Y = @1; Probability = @2", tostring(i
.ypos
), tostring(i
.prob
)))
528 slices
= slices
..insert
..","
530 slices
= slices
:sub(1, -2) -- Remove final comma
534 table[0,0;6.8,6;slices;]]..slices
..[[;]]..selected
..[[]
537 if self
.panel_add
or self
.panel_edit
then
538 local ypos_default
, prob_default
= "", ""
539 local done_button
= "button[5,7.18;2,1;done_add;"..F(S("Done")).."]"
540 if self
.panel_edit
then
541 done_button
= "button[5,7.18;2,1;done_edit;"..F(S("Done")).."]"
542 if slice_list
[self
.selected
] then
543 ypos_default
= slice_list
[self
.selected
].ypos
544 prob_default
= slice_list
[self
.selected
].prob
549 field[0.3,7.5;2.5,1;ypos;]]..F(S("Y position (max. @1):", (meta
.y_size
- 1)))..[[;]]..ypos_default
..[[]
550 field[2.8,7.5;2.5,1;prob;]]..F(S("Probability (0-255):"))..[[;]]..prob_default
..[[]
551 field_close_on_enter[ypos;false]
552 field_close_on_enter[prob;false]
556 if not self
.panel_edit
then
557 form
= form
.."button[0,6;2.4,1;add;"..F(S("+ Add slice")).."]"
560 if slices
~= "" and self
.selected
and not self
.panel_add
then
561 if not self
.panel_edit
then
563 button[2.4,6;2.4,1;remove;]]..F(S("- Remove slice"))..[[]
564 button[4.8,6;2.4,1;edit;]]..F(S("+/- Edit slice"))..[[]
568 button[2.4,6;2.4,1;remove;]]..F(S("- Remove slice"))..[[]
569 button[4.8,6;2.4,1;edit;]]..F(S("+/- Edit slice"))..[[]
576 handle
= function(self
, pos
, name
, fields
)
577 if not check_priv(name
) then
581 local meta
= minetest
.get_meta(pos
)
582 local player
= minetest
.get_player_by_name(name
)
584 if fields
.slices
then
585 local slices
= fields
.slices
:split(":")
586 self
.selected
= tonumber(slices
[2])
590 if not self
.panel_add
then
591 self
.panel_add
= true
592 schemedit
.show_formspec(pos
, player
, "slice")
595 schemedit
.show_formspec(pos
, player
, "slice")
599 local ypos
, prob
= tonumber(fields
.ypos
), tonumber(fields
.prob
)
600 if (fields
.done_add
or fields
.done_edit
) and fields
.ypos
and fields
.prob
and
601 fields
.ypos
~= "" and fields
.prob
~= "" and ypos
and prob
and
602 ypos
<= (meta
:get_int("y_size") - 1) and prob
>= 0 and prob
<= 255 then
603 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
604 local index
= #slice_list
+ 1
605 if fields
.done_edit
then
606 index
= self
.selected
609 slice_list
[index
] = {ypos
= ypos
, prob
= prob
}
611 meta
:set_string("slices", minetest
.serialize(slice_list
))
613 -- Update and show formspec
615 schemedit
.show_formspec(pos
, player
, "slice")
618 if fields
.remove and self
.selected
then
619 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
620 slice_list
[self
.selected
] = nil
621 meta
:set_string("slices", minetest
.serialize(renumber(slice_list
)))
625 self
.panel_edit
= nil
626 schemedit
.show_formspec(pos
, player
, "slice")
630 if not self
.panel_edit
then
631 self
.panel_edit
= true
632 schemedit
.show_formspec(pos
, player
, "slice")
634 self
.panel_edit
= nil
635 schemedit
.show_formspec(pos
, player
, "slice")
641 schemedit
.add_form("probtool", {
643 caption
= S("Schematic Node Probability Tool"),
644 get
= function(self
, pos
, name
)
645 local player
= minetest
.get_player_by_name(name
)
649 local probtool
= player
:get_wielded_item()
650 if probtool
:get_name() ~= "schemedit:probtool" then
654 local meta
= probtool
:get_meta()
655 local prob
= tonumber(meta
:get_string("schemedit_prob"))
656 local force_place
= meta
:get_string("schemedit_force_place")
661 if force_place
== nil or force_place
== "" then
662 force_place
= "false"
664 local form
= "size[5,4]"..
665 "label[0,0;"..F(S("Schematic Node Probability Tool")).."]"..
666 "field[0.75,1;4,1;prob;"..F(S("Probability (0-255)"))..";"..prob
.."]"..
667 "checkbox[0.60,1.5;force_place;"..F(S("Force placement"))..";" .. force_place
.. "]" ..
668 "button_exit[0.25,3;2,1;cancel;"..F(S("Cancel")).."]"..
669 "button_exit[2.75,3;2,1;submit;"..F(S("Apply")).."]"..
670 "tooltip[prob;"..F(S("Probability that the node will be placed")).."]"..
671 "tooltip[force_place;"..F(S("If enabled, the node will replace nodes other than air and ignore")).."]"..
672 "field_close_on_enter[prob;false]"
675 handle
= function(self
, pos
, name
, fields
)
676 if not check_priv(name
) then
680 if fields
.submit
then
681 local prob
= tonumber(fields
.prob
)
683 local player
= minetest
.get_player_by_name(name
)
687 local probtool
= player
:get_wielded_item()
688 if probtool
:get_name() ~= "schemedit:probtool" then
692 local force_place
= self
.force_place
== true
694 set_item_metadata(probtool
, prob
, force_place
)
696 -- Repurpose the tool's wear bar to display the set probability
697 probtool
:set_wear(math
.floor(((255-prob
)/255)*65535))
699 player
:set_wielded_item(probtool
)
702 if fields
.force_place
== "true" then
703 self
.force_place
= true
704 elseif fields
.force_place
== "false" then
705 self
.force_place
= false
714 --- Copies and modifies positions `pos1` and `pos2` so that each component of
715 -- `pos1` is less than or equal to the corresponding component of `pos2`.
716 -- Returns the new positions.
717 function schemedit
.sort_pos(pos1
, pos2
)
718 if not pos1
or not pos2
then
722 pos1
, pos2
= table.copy(pos1
), table.copy(pos2
)
723 if pos1
.x
> pos2
.x
then
724 pos2
.x
, pos1
.x
= pos1
.x
, pos2
.x
726 if pos1
.y
> pos2
.y
then
727 pos2
.y
, pos1
.y
= pos1
.y
, pos2
.y
729 if pos1
.z
> pos2
.z
then
730 pos2
.z
, pos1
.z
= pos1
.z
, pos2
.z
735 -- [function] Prepare size
736 function schemedit
.size(pos
)
737 local pos1
= vector
.new(pos
)
738 local meta
= minetest
.get_meta(pos
)
739 local node
= minetest
.get_node(pos
)
740 local param2
= node
.param2
742 x
= meta
:get_int("x_size"),
743 y
= math
.max(meta
:get_int("y_size") - 1, 0),
744 z
= meta
:get_int("z_size"),
748 local new_pos
= vector
.add({x
= size
.z
, y
= size
.y
, z
= -size
.x
}, pos
)
750 new_pos
.z
= new_pos
.z
+ 1
752 elseif param2
== 2 then
753 local new_pos
= vector
.add({x
= -size
.x
, y
= size
.y
, z
= -size
.z
}, pos
)
755 new_pos
.x
= new_pos
.x
+ 1
757 elseif param2
== 3 then
758 local new_pos
= vector
.add({x
= -size
.z
, y
= size
.y
, z
= size
.x
}, pos
)
760 new_pos
.z
= new_pos
.z
- 1
763 local new_pos
= vector
.add(size
, pos
)
765 new_pos
.x
= new_pos
.x
- 1
770 -- [function] Mark region
771 function schemedit
.mark(pos
)
772 schemedit
.unmark(pos
)
774 local id
= minetest
.hash_node_position(pos
)
775 local owner
= minetest
.get_meta(pos
):get_string("owner")
776 local pos1
, pos2
= schemedit
.size(pos
)
777 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
779 local thickness
= 0.2
780 local sizex
, sizey
, sizez
= (1 + pos2
.x
- pos1
.x
) / 2, (1 + pos2
.y
- pos1
.y
) / 2, (1 + pos2
.z
- pos1
.z
) / 2
786 for _
, z
in ipairs({pos1
.z
- 0.5, pos2
.z
+ 0.5}) do
792 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= pos1
.y
+ sizey
- 0.5, z
= z
+ offset
}, "schemedit:display")
793 if marker
~= nil then
794 marker
:set_properties({
795 visual_size
={x
=(sizex
+0.01) * 2, y
=(sizey
+0.01) * 2},
797 marker
:get_luaentity().id
= id
798 marker
:get_luaentity().owner
= owner
799 table.insert(m
, marker
)
806 for _
, x
in ipairs({pos1
.x
- 0.5, pos2
.x
+ 0.5}) do
813 local marker
= minetest
.add_entity({x
= x
+ offset
, y
= pos1
.y
+ sizey
- 0.5, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
814 if marker
~= nil then
815 marker
:set_properties({
816 visual_size
={x
=(sizez
+0.01) * 2, y
=(sizey
+0.01) * 2},
818 marker
:set_rotation({x
=0, y
=math
.pi
/ 2, z
=0})
819 marker
:get_luaentity().id
= id
820 marker
:get_luaentity().owner
= owner
821 table.insert(m
, marker
)
828 for _
, y
in ipairs({pos1
.y
- 0.5, pos2
.y
+ 0.5}) do
835 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= y
+ offset
, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
836 if marker
~= nil then
837 marker
:set_properties({
838 visual_size
={x
=(sizex
+0.01) * 2, y
=(sizez
+0.01) * 2},
840 marker
:set_rotation({x
=math
.pi
/2, y
=0, z
=0})
841 marker
:get_luaentity().id
= id
842 marker
:get_luaentity().owner
= owner
843 table.insert(m
, marker
)
850 schemedit
.markers
[id
] = m
854 -- [function] Unmark region
855 function schemedit
.unmark(pos
)
856 local id
= minetest
.hash_node_position(pos
)
857 if schemedit
.markers
[id
] then
859 for _
, entity
in ipairs(schemedit
.markers
[id
]) do
868 --- Mark node probability values near player
871 -- Show probability and force_place status of a particular position for player in HUD.
872 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
873 -- The distance to the node is also displayed below that. This can't be avoided and is
874 -- and artifact of the waypoint HUD element.
875 function schemedit
.display_node_prob(player
, pos
, prob
, force_place
)
877 if prob
and force_place
== true then
878 wpstring
= string.format("%s [F]", prob
)
879 elseif prob
and type(tonumber(prob
)) == "number" then
881 elseif force_place
== true then
885 return player
:hud_add({
886 hud_elem_type
= "waypoint",
889 text
= "m", -- For the distance artifact
890 number = text_color_number
,
896 -- Display the node probabilities and force_place status of the nodes in a region.
897 -- By default, this is done for nodes near the player (distance: 5).
898 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
899 function schemedit
.display_node_probs_region(player
, pos1
, pos2
)
900 local playername
= player
:get_player_name()
901 local pos
= vector
.round(player
:get_pos())
904 -- Default: 5 nodes away from player in any direction
906 pos1
= vector
.subtract(pos
, dist
)
909 pos2
= vector
.add(pos
, dist
)
911 for x
=pos1
.x
, pos2
.x
do
912 for y
=pos1
.y
, pos2
.y
do
913 for z
=pos1
.z
, pos2
.z
do
914 local checkpos
= {x
=x
, y
=y
, z
=z
}
915 local nodehash
= minetest
.hash_node_position(checkpos
)
917 -- If node is already displayed, remove it so it can re replaced later
918 if displayed_waypoints
[playername
][nodehash
] then
919 player
:hud_remove(displayed_waypoints
[playername
][nodehash
])
920 displayed_waypoints
[playername
][nodehash
] = nil
923 local prob
, force_place
924 local meta
= minetest
.get_meta(checkpos
)
925 prob
= meta
:get_string("schemedit_prob")
926 force_place
= meta
:get_string("schemedit_force_place") == "true"
927 local hud_id
= schemedit
.display_node_prob(player
, checkpos
, prob
, force_place
)
929 displayed_waypoints
[playername
][nodehash
] = hud_id
930 displayed_waypoints
[playername
].display_active
= true
937 -- Remove all active displayed node statuses.
938 function schemedit
.clear_displayed_node_probs(player
)
939 local playername
= player
:get_player_name()
940 for nodehash
, hud_id
in pairs(displayed_waypoints
[playername
]) do
941 player
:hud_remove(hud_id
)
942 displayed_waypoints
[playername
][nodehash
] = nil
943 displayed_waypoints
[playername
].display_active
= false
947 minetest
.register_on_joinplayer(function(player
)
948 displayed_waypoints
[player
:get_player_name()] = {
949 display_active
= false -- If true, there *might* be at least one active node prob HUD display
950 -- If false, no node probabilities are displayed for sure.
954 minetest
.register_on_leaveplayer(function(player
)
955 displayed_waypoints
[player
:get_player_name()] = nil
958 -- Regularily clear the displayed node probabilities and force_place
959 -- for all players who do not wield the probtool.
960 -- This makes sure the screen is not spammed with information when it
963 minetest
.register_globalstep(function(dtime
)
964 cleartimer
= cleartimer
+ dtime
965 if cleartimer
> 2 then
966 local players
= minetest
.get_connected_players()
967 for p
= 1, #players
do
968 local player
= players
[p
]
969 local pname
= player
:get_player_name()
970 if displayed_waypoints
[pname
].display_active
then
971 local item
= player
:get_wielded_item()
972 if item
:get_name() ~= "schemedit:probtool" then
973 schemedit
.clear_displayed_node_probs(player
)
985 -- [priv] schematic_override
986 minetest
.register_privilege("schematic_override", {
987 description
= S("Allows you to access schemedit nodes not owned by you"),
988 give_to_singleplayer
= false,
991 local help_import
= ""
993 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"
996 -- [node] Schematic creator
997 minetest
.register_node("schemedit:creator", {
998 description
= S("Schematic Creator"),
999 _doc_items_longdesc
= S("The schematic creator is used to save a region of the world into a schematic file (.mts)."),
1000 _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"..
1001 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"..
1003 S("The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.").."\n\n"..
1004 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"..
1005 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."),
1006 tiles
= {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
1007 "schemedit_creator_sides.png"},
1008 groups
= { dig_immediate
= 2},
1009 paramtype2
= "facedir",
1010 is_ground_content
= false,
1012 after_place_node
= function(pos
, player
)
1013 local name
= player
:get_player_name()
1014 local meta
= minetest
.get_meta(pos
)
1016 meta
:set_string("owner", name
)
1017 meta
:set_string("infotext", S("Schematic Creator").."\n"..S("(owned by @1)", name
))
1018 meta
:set_string("prob_list", minetest
.serialize({}))
1019 meta
:set_string("slices", minetest
.serialize({}))
1021 local node
= minetest
.get_node(pos
)
1022 local dir
= minetest
.facedir_to_dir(node
.param2
)
1024 meta
:set_int("x_size", 1)
1025 meta
:set_int("y_size", 1)
1026 meta
:set_int("z_size", 1)
1028 -- Don't take item from itemstack
1031 can_dig
= function(pos
, player
)
1032 local name
= player
:get_player_name()
1033 local meta
= minetest
.get_meta(pos
)
1034 if meta
:get_string("owner") == name
or
1035 minetest
.check_player_privs(player
, "schematic_override") == true then
1041 on_rightclick
= function(pos
, node
, player
)
1042 local meta
= minetest
.get_meta(pos
)
1043 local name
= player
:get_player_name()
1044 if meta
:get_string("owner") == name
or
1045 minetest
.check_player_privs(player
, "schematic_override") == true then
1046 -- Get player attribute
1047 local tab
= player
:get_attribute("schemedit:tab")
1048 if not forms
[tab
] or not tab
then
1052 schemedit
.show_formspec(pos
, player
, tab
, true)
1055 after_destruct
= function(pos
)
1056 schemedit
.unmark(pos
)
1059 -- No support for Minetest Game's screwdriver
1063 minetest
.register_tool("schemedit:probtool", {
1064 description
= S("Schematic Node Probability Tool"),
1065 _doc_items_longdesc
=
1066 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"..
1067 S("It allows you to set two things:").."\n"..
1068 S("1) Set probability: Chance for any particular node to be actually placed (default: always placed)").."\n"..
1069 S("2) Enable force placement: These nodes replace node other than air and ignore when placed in a schematic (default: off)"),
1070 _doc_items_usagehelp
= "\n"..
1071 S("BASIC USAGE:").."\n"..
1072 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"..
1073 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"..
1074 S("NODE HUD:").."\n"..
1075 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"..
1076 S("To disable the node HUD, unselect the tool or hit “place” while not pointing anything.").."\n\n"..
1077 S("UPDATING THE NODE HUD:").."\n"..
1078 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."),
1079 wield_image
= "schemedit_probtool.png",
1080 inventory_image
= "schemedit_probtool.png",
1081 liquids_pointable
= true,
1082 groups
= { disable_repair
= 1 },
1083 on_use
= function(itemstack
, user
, pointed_thing
)
1084 local uname
= user
:get_player_name()
1085 if uname
and not check_priv(uname
) then
1089 local ctrl
= user
:get_player_control()
1091 if not ctrl
.sneak
then
1092 -- Open dialog to change the probability to apply to nodes
1093 schemedit
.show_formspec(user
:get_pos(), user
, "probtool", true)
1097 -- Display the probability and force_place values for nodes.
1099 -- If a schematic creator was punched, only enable display for all nodes
1100 -- within the creator's region.
1101 local use_creator_region
= false
1102 if pointed_thing
and pointed_thing
.type == "node" and pointed_thing
.under
then
1103 local punchpos
= pointed_thing
.under
1104 local node
= minetest
.get_node(punchpos
)
1105 if node
.name
== "schemedit:creator" then
1106 local pos1
, pos2
= schemedit
.size(punchpos
)
1107 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
1108 schemedit
.display_node_probs_region(user
, pos1
, pos2
)
1113 -- Otherwise, just display the region close to the player
1114 schemedit
.display_node_probs_region(user
)
1117 on_secondary_use
= function(itemstack
, user
, pointed_thing
)
1118 schemedit
.clear_displayed_node_probs(user
)
1120 -- Set note probability and force_place and enable node probability display
1121 on_place
= function(itemstack
, placer
, pointed_thing
)
1122 -- Use pointed node's on_rightclick function first, if present
1123 local node
= minetest
.get_node(pointed_thing
.under
)
1124 if placer
and not placer
:get_player_control().sneak
then
1125 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
1126 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
1130 -- This sets the node probability of pointed node to the
1131 -- currently used probability stored in the tool.
1132 local pos
= pointed_thing
.under
1133 local node
= minetest
.get_node(pos
)
1134 -- Schematic void are ignored, they always have probability 0
1135 if node
.name
== "schemedit:void" then
1138 local nmeta
= minetest
.get_meta(pos
)
1139 local imeta
= itemstack
:get_meta()
1140 local prob
= tonumber(imeta
:get_string("schemedit_prob"))
1141 local force_place
= imeta
:get_string("schemedit_force_place")
1143 if not prob
or prob
== 255 then
1144 nmeta
:set_string("schemedit_prob", nil)
1146 nmeta
:set_string("schemedit_prob", prob
)
1148 if force_place
== "true" then
1149 nmeta
:set_string("schemedit_force_place", "true")
1151 nmeta
:set_string("schemedit_force_place", nil)
1154 -- Enable node probablity display
1155 schemedit
.display_node_probs_region(placer
)
1161 minetest
.register_node("schemedit:void", {
1162 description
= S("Schematic Void"),
1163 _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."),
1164 _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."),
1165 tiles
= { "schemedit_void.png" },
1166 drawtype
= "nodebox",
1167 is_ground_content
= false,
1168 paramtype
= "light",
1170 sunlight_propagates
= true,
1174 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
1177 groups
= { dig_immediate
= 3},
1180 -- [entity] Visible schematic border
1181 minetest
.register_entity("schemedit:display", {
1182 visual
= "upright_sprite",
1183 textures
= {"schemedit_border.png"},
1184 visual_size
= {x
=10, y
=10},
1187 static_save
= false,
1188 glow
= minetest
.LIGHT_MAX
,
1190 on_step
= function(self
, dtime
)
1192 self
.object
:remove()
1193 elseif not schemedit
.markers
[self
.id
] then
1194 self
.object
:remove()
1197 on_activate
= function(self
)
1198 self
.object
:set_armor_groups({immortal
= 1})
1202 minetest
.register_lbm({
1203 label
= "Reset schematic creator border entities",
1204 name
= "schemedit:reset_border",
1205 nodenames
= "schemedit:creator",
1206 run_at_every_load
= true,
1207 action
= function(pos
, node
)
1208 local meta
= minetest
.get_meta(pos
)
1209 meta
:set_string("schem_border", "false")
1213 local function add_suffix(schem
)
1214 -- Automatically add file name suffix if omitted
1215 local schem_full
, schem_lua
1216 if string.sub(schem
, string.len(schem
)-3, string.len(schem
)) == ".mts" then
1218 schem_lua
= string.sub(schem
, 1, -5) .. ".lua"
1220 schem_full
= schem
.. ".mts"
1221 schem_lua
= schem
.. ".lua"
1223 return schem_full
, schem_lua
1226 -- [chatcommand] Place schematic
1227 minetest
.register_chatcommand("placeschem", {
1228 description
= S("Place schematic at the position specified or the current player position (loaded from @1)", export_path_trunc
),
1229 privs
= {server
= true},
1230 params
= S("<schematic name>[.mts] [<x> <y> <z>]"),
1231 func
= function(name
, param
)
1232 local schem
, p
= string.match(param
, "^([^ ]+) *(.*)$")
1233 local pos
= minetest
.string_to_pos(p
)
1236 return false, S("No schematic file specified.")
1240 pos
= minetest
.get_player_by_name(name
):get_pos()
1243 local schem_full
, schem_lua
= add_suffix(schem
)
1244 local success
= false
1245 local schem_path
= export_path_full
.. DIR_DELIM
.. schem_full
1246 if minetest
.read_schematic
then
1247 -- We don't call minetest.place_schematic with the path name directly because
1248 -- this would trigger the caching and we wouldn't get any updates to the schematic
1249 -- files when we reload. minetest.read_schematic circumvents that.
1250 local schematic
= minetest
.read_schematic(schem_path
, {})
1252 success
= minetest
.place_schematic(pos
, schematic
, "random", nil, false)
1255 -- Legacy support for Minetest versions that do not have minetest.read_schematic
1256 success
= minetest
.place_schematic(schem_path
, schematic
, "random", nil, false)
1259 if success
== nil then
1260 return false, S("Schematic file could not be loaded!")
1268 -- [chatcommand] Convert MTS schematic file to .lua file
1269 minetest
.register_chatcommand("mts2lua", {
1270 description
= S("Convert .mts schematic file to .lua file (loaded from @1)", export_path_trunc
),
1271 privs
= {server
= true},
1272 params
= S("<schematic name>[.mts] [comments]"),
1273 func
= function(name
, param
)
1274 local schem
, comments_str
= string.match(param
, "^([^ ]+) *(.*)$")
1277 return false, S("No schematic file specified.")
1280 local comments
= comments_str
== "comments"
1282 -- Automatically add file name suffix if omitted
1283 local schem_full
, schem_lua
= add_suffix(schem
)
1284 local schem_path
= export_path_full
.. DIR_DELIM
.. schem_full
1285 local schematic
= minetest
.read_schematic(schem_path
, {})
1288 local str
= minetest
.serialize_schematic(schematic
, "lua", {lua_use_comments
=comments
})
1289 local lua_path
= export_path_full
.. DIR_DELIM
.. schem_lua
1290 local file
= io
.open(lua_path
, "w")
1291 if file
and str
then
1295 return true, S("Exported schematic to @1", lua_path
)
1297 return false, S("Failed!")