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 local can_import
= minetest
.read_schematic
~= nil
20 schemedit
.markers
= {}
22 -- [local function] Renumber table
23 local function renumber(t
)
25 for _
, i
in pairs(t
) do
39 local displayed_waypoints
= {}
41 -- Sadly, the probabilities presented in Lua (0-255) are not identical to the REAL probabilities in the
42 -- schematic file (0-127). There are two converter functions to convert from one probability type to another.
43 -- This mod tries to retain the “Lua probability” as long as possible and only switches to “schematic probability”
44 -- on an actual export to a schematic.
46 function schemedit
.lua_prob_to_schematic_prob(lua_prob
)
47 return math
.floor(lua_prob
/ 2)
50 function schemedit
.schematic_prob_to_lua_prob(schematic_prob
)
51 return schematic_prob
* 2
55 -- [function] Add form
56 function schemedit
.add_form(name
, def
)
61 tabs
[#tabs
+ 1] = name
65 -- [function] Generate tabs
66 function schemedit
.generate_tabs(current
)
67 local retval
= "tabheader[0,0;tabs;"
68 for _
, t
in pairs(tabs
) do
70 if f
.tab
~= false and f
.caption
then
71 retval
= retval
..f
.caption
..","
73 if type(current
) ~= "number" and current
== f
.name
then
78 retval
= retval
:sub(1, -2) -- Strip last comma
79 retval
= retval
..";"..current
.."]" -- Close tabheader
83 -- [function] Handle tabs
84 function schemedit
.handle_tabs(pos
, name
, fields
)
85 local tab
= tonumber(fields
.tabs
)
86 if tab
and tabs
[tab
] and forms
[tabs
[tab]]
then
87 schemedit
.show_formspec(pos
, name
, forms
[tabs
[tab]]
.name
)
92 -- [function] Show formspec
93 function schemedit
.show_formspec(pos
, player
, tab
, show
, ...)
95 if type(player
) == "string" then
96 player
= minetest
.get_player_by_name(player
)
98 local name
= player
:get_player_name()
100 if show
~= false then
101 if not form_data
[name
] then
105 local form
= forms
[tab
].get(form_data
[name
], pos
, name
, ...)
106 if forms
[tab
].tab
then
107 form
= form
..schemedit
.generate_tabs(tab
)
110 minetest
.show_formspec(name
, "schemedit:"..tab
, form
)
113 -- Update player attribute
114 if forms
[tab
].cache_name
~= false then
115 player
:set_attribute("schemedit:tab", tab
)
118 minetest
.close_formspec(pname
, "schemedit:"..tab
)
123 -- [event] On receive fields
124 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
125 local formname
= formname
:split(":")
127 if formname
[1] == "schemedit" and forms
[formname
[2]]
then
128 local handle
= forms
[formname
[2]]
.handle
129 local name
= player
:get_player_name()
130 if contexts
[name
] then
131 if not form_data
[name
] then
135 if not schemedit
.handle_tabs(contexts
[name
], name
, fields
) and handle
then
136 handle(form_data
[name
], contexts
[name
], name
, fields
)
142 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
143 schemedit
.scan_metadata
= function(pos1
, pos2
)
146 for x
=pos1
.x
, pos2
.x
do
147 for y
=pos1
.y
, pos2
.y
do
148 for z
=pos1
.z
, pos2
.z
do
149 local scanpos
= {x
=x
, y
=y
, z
=z
}
150 local node
= minetest
.get_node_or_nil(scanpos
)
152 local prob
, force_place
153 if node
== nil or node
.name
== "schemedit:void" then
157 local meta
= minetest
.get_meta(scanpos
)
159 prob
= tonumber(meta
:get_string("schemedit_prob")) or 255
160 local fp
= meta
:get_string("schemedit_force_place")
168 local hashpos
= minetest
.hash_node_position(scanpos
)
169 prob_list
[hashpos
] = {
172 force_place
= force_place
,
181 -- Sets probability and force_place metadata of an item.
182 -- Also updates item description.
183 -- The itemstack is updated in-place.
184 local function set_item_metadata(itemstack
, prob
, force_place
)
185 local smeta
= itemstack
:get_meta()
186 local prob_desc
= "\n"..S("Probability: @1", prob
or
187 smeta
:get_string("schemedit_prob") or S("Not Set"))
188 -- Update probability
189 if prob
and prob
>= 0 and prob
< 255 then
190 smeta
:set_string("schemedit_prob", tostring(prob
))
191 elseif prob
and prob
== 255 then
192 -- Clear prob metadata for default probability
194 smeta
:set_string("schemedit_prob", nil)
196 prob_desc
= "\n"..S("Probability: @1", smeta
:get_string("schemedit_prob") or
200 -- Update force place
201 if force_place
== true then
202 smeta
:set_string("schemedit_force_place", "true")
203 elseif force_place
== false then
204 smeta
:set_string("schemedit_force_place", nil)
207 -- Update description
208 local desc
= minetest
.registered_items
[itemstack
:get_name()].description
209 local meta_desc
= smeta
:get_string("description")
210 if meta_desc
and meta_desc
~= "" then
214 local original_desc
= smeta
:get_string("original_description")
215 if original_desc
and original_desc
~= "" then
218 smeta
:set_string("original_description", desc
)
221 local force_desc
= ""
222 if smeta
:get_string("schemedit_force_place") == "true" then
223 force_desc
= "\n"..S("Force placement")
226 desc
= desc
..minetest
.colorize(text_color
, prob_desc
..force_desc
)
228 smeta
:set_string("description", desc
)
236 local import_btn
= ""
238 import_btn
= "button[0.5,2.5;6,1;import;"..F(S("Import schematic")).."]"
240 schemedit
.add_form("main", {
243 get
= function(self
, pos
, name
)
244 local meta
= minetest
.get_meta(pos
):to_table().fields
245 local strpos
= minetest
.pos_to_string(pos
)
246 local hashpos
= minetest
.hash_node_position(pos
)
249 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
250 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Hide border")).."]"
252 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Show border")).."]"
255 -- TODO: Show information regarding volume, pos1, pos2, etc... in formspec
258 label[0.5,-0.1;]]..F(S("Position: @1", strpos
))..[[]
259 label[3,-0.1;]]..F(S("Owner: @1", name
))..[[]
261 field[0.8,1;5,1;name;]]..F(S("Schematic name:"))..[[;]]..F(meta
.schem_name
or "")..[[]
262 button[5.3,0.69;1.2,1;save_name;]]..F(S("OK"))..[[]
263 tooltip[save_name;]]..F(S("Save schematic name"))..[[]
264 field_close_on_enter[name;false]
266 button[0.5,1.5;6,1;export;]]..F(S("Export schematic")).."]"..
268 textarea[0.8,3.5;6.2,5;;]]..F(S("Export/import path:\n@1",
269 export_path_trunc
.. DIR_DELIM
.. F(S("<name>"))..".mts"))..[[;]
270 field[0.8,7;2,1;x;]]..F(S("X size:"))..[[;]]..meta
.x_size
..[[]
271 field[2.8,7;2,1;y;]]..F(S("Y size:"))..[[;]]..meta
.y_size
..[[]
272 field[4.8,7;2,1;z;]]..F(S("Z size:"))..[[;]]..meta
.z_size
..[[]
273 field_close_on_enter[x;false]
274 field_close_on_enter[y;false]
275 field_close_on_enter[z;false]
277 button[0.5,7.5;3,1;save;]]..F(S("Save size"))..[[]
280 if minetest
.get_modpath("doc") then
281 form
= form
.. "image_button[6.4,-0.2;0.8,0.8;doc_button_icon_lores.png;doc;]" ..
282 "tooltip[doc;"..F(S("Help")).."]"
286 handle
= function(self
, pos
, name
, fields
)
287 local realmeta
= minetest
.get_meta(pos
)
288 local meta
= realmeta
:to_table().fields
289 local hashpos
= minetest
.hash_node_position(pos
)
291 -- Save size vector values
292 if (fields
.x
and fields
.x
~= "") then
293 local x
= tonumber(fields
.x
)
295 meta
.x_size
= math
.max(x
, 1)
298 if (fields
.y
and fields
.y
~= "") then
299 local y
= tonumber(fields
.y
)
301 meta
.y_size
= math
.max(y
, 1)
304 if (fields
.z
and fields
.z
~= "") then
305 local z
= tonumber(fields
.z
)
307 meta
.z_size
= math
.max(z
, 1)
311 -- Save schematic name
313 meta
.schem_name
= fields
.name
317 doc
.show_entry(name
, "nodes", "schemedit:creator", true)
322 if fields
.border
then
323 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
324 schemedit
.unmark(pos
)
325 meta
.schem_border
= "false"
328 meta
.schem_border
= "true"
333 if fields
.export
and meta
.schem_name
and meta
.schem_name
~= "" then
334 local pos1
, pos2
= schemedit
.size(pos
)
335 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
336 local path
= export_path_full
.. DIR_DELIM
339 local plist
= schemedit
.scan_metadata(pos1
, pos2
)
340 local probability_list
= {}
341 for hash
, i
in pairs(plist
) do
342 local prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
)
343 if i
.force_place
== true then
347 table.insert(probability_list
, {
348 pos
= minetest
.get_position_from_hash(hash
),
353 local slist
= minetest
.deserialize(meta
.slices
)
354 local slice_list
= {}
355 for _
, i
in pairs(slist
) do
356 slice_list
[#slice_list
+ 1] = {
357 ypos
= pos
.y
+ i
.ypos
,
358 prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
),
362 local filepath
= path
..meta
.schem_name
..".mts"
363 local res
= minetest
.create_schematic(pos1
, pos2
, probability_list
, filepath
, slice_list
)
366 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
367 S("Exported schematic to @1", filepath
)))
369 minetest
.chat_send_player(name
, minetest
.colorize("red",
370 S("Failed to export schematic to @1", filepath
)))
375 if fields
.import
and meta
.schem_name
and meta
.schem_name
~= "" then
376 if not minetest
.get_player_privs(name
).debug
then
377 minetest
.chat_send_player(name
, minetest
.colorize("red",
378 S("Insufficient privileges! You need the “debug” privilege to do this.")))
382 if not can_import
then
386 local node
= minetest
.get_node(pos
)
387 local path
= export_path_full
.. DIR_DELIM
389 local filepath
= path
..meta
.schem_name
..".mts"
390 local schematic
= minetest
.read_schematic(filepath
, {write_yslice_prob
="low"})
391 local success
= false
394 meta
.x_size
= schematic
.size
.x
395 meta
.y_size
= schematic
.size
.y
396 meta
.z_size
= schematic
.size
.z
397 meta
.slices
= minetest
.serialize(schematic
.yslice_prob
)
399 if node
.param2
== 1 then
400 pos1
= vector
.add(pos
, {x
=1,y
=0,z
=-meta
.z_size
+1})
401 elseif node
.param2
== 2 then
402 pos1
= vector
.add(pos
, {x
=-meta
.x_size
+1,y
=0,z
=-meta
.z_size
})
403 elseif node
.param2
== 3 then
404 pos1
= vector
.add(pos
, {x
=-meta
.x_size
,y
=0,z
=0})
406 pos1
= vector
.add(pos
, {x
=0,y
=0,z
=1})
409 local schematic_for_meta
= table.copy(schematic
)
410 -- Strip probability data for placement
411 schematic
.yslice_prob
= {}
412 for d
=1, #schematic
.data
do
413 schematic
.data
[d
].prob
= nil
417 success
= minetest
.place_schematic(pos1
, schematic
, "0", nil, true)
419 -- Add special schematic data to nodes
422 for z
=0, meta
.z_size
-1 do
423 for y
=0, meta
.y_size
-1 do
424 for x
=0, meta
.x_size
-1 do
425 local data
= schematic_for_meta
.data
[d
]
426 local pp
= {x
=pos1
.x
+x
, y
=pos1
.y
+y
, z
=pos1
.z
+z
}
427 if data
.prob
== 0 then
428 minetest
.set_node(pp
, {name
="schemedit:void"})
430 local meta
= minetest
.get_meta(pp
)
431 if data
.prob
and data
.prob
~= 255 and data
.prob
~= 254 then
432 meta
:set_string("schemedit_prob", tostring(data
.prob
))
434 meta
:set_string("schemedit_prob", "")
436 if data
.force_place
then
437 meta
:set_string("schemedit_force_place", "true")
439 meta
:set_string("schemedit_force_place", "")
449 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
450 S("Imported schematic from @1", filepath
)))
452 minetest
.chat_send_player(name
, minetest
.colorize("red",
453 S("Failed to import schematic from @1", filepath
)))
459 -- Save meta before updating visuals
460 local inv
= realmeta
:get_inventory():get_lists()
461 realmeta
:from_table({fields
= meta
, inventory
= inv
})
464 if not fields
.border
and meta
.schem_border
== "true" then
469 if not fields
.quit
then
470 schemedit
.show_formspec(pos
, minetest
.get_player_by_name(name
), "main")
475 schemedit
.add_form("slice", {
476 caption
= S("Y Slices"),
478 get
= function(self
, pos
, name
, visible_panel
)
479 local meta
= minetest
.get_meta(pos
):to_table().fields
481 self
.selected
= self
.selected
or 1
482 local selected
= tostring(self
.selected
)
483 local slice_list
= minetest
.deserialize(meta
.slices
)
485 for _
, i
in pairs(slice_list
) do
486 local insert
= F(S("Y = @1; Probability = @2", tostring(i
.ypos
), tostring(i
.prob
)))
487 slices
= slices
..insert
..","
489 slices
= slices
:sub(1, -2) -- Remove final comma
493 table[0,0;6.8,6;slices;]]..slices
..[[;]]..selected
..[[]
496 if self
.panel_add
or self
.panel_edit
then
497 local ypos_default
, prob_default
= "", ""
498 local done_button
= "button[5,7.18;2,1;done_add;"..F(S("Done")).."]"
499 if self
.panel_edit
then
500 done_button
= "button[5,7.18;2,1;done_edit;"..F(S("Done")).."]"
501 if slice_list
[self
.selected
] then
502 ypos_default
= slice_list
[self
.selected
].ypos
503 prob_default
= slice_list
[self
.selected
].prob
508 field[0.3,7.5;2.5,1;ypos;]]..F(S("Y position (max. @1):", (meta
.y_size
- 1)))..[[;]]..ypos_default
..[[]
509 field[2.8,7.5;2.5,1;prob;]]..F(S("Probability (0-255):"))..[[;]]..prob_default
..[[]
510 field_close_on_enter[ypos;false]
511 field_close_on_enter[prob;false]
515 if not self
.panel_edit
then
516 form
= form
.."button[0,6;2.4,1;add;"..F(S("+ Add slice")).."]"
519 if slices
~= "" and self
.selected
and not self
.panel_add
then
520 if not self
.panel_edit
then
522 button[2.4,6;2.4,1;remove;]]..F(S("- Remove slice"))..[[]
523 button[4.8,6;2.4,1;edit;]]..F(S("+/- Edit slice"))..[[]
527 button[2.4,6;2.4,1;remove;]]..F(S("- Remove slice"))..[[]
528 button[4.8,6;2.4,1;edit;]]..F(S("+/- Edit slice"))..[[]
535 handle
= function(self
, pos
, name
, fields
)
536 local meta
= minetest
.get_meta(pos
)
537 local player
= minetest
.get_player_by_name(name
)
539 if fields
.slices
then
540 local slices
= fields
.slices
:split(":")
541 self
.selected
= tonumber(slices
[2])
545 if not self
.panel_add
then
546 self
.panel_add
= true
547 schemedit
.show_formspec(pos
, player
, "slice")
550 schemedit
.show_formspec(pos
, player
, "slice")
554 local ypos
, prob
= tonumber(fields
.ypos
), tonumber(fields
.prob
)
555 if (fields
.done_add
or fields
.done_edit
) and fields
.ypos
and fields
.prob
and
556 fields
.ypos
~= "" and fields
.prob
~= "" and ypos
and prob
and
557 ypos
<= (meta
:get_int("y_size") - 1) and prob
>= 0 and prob
<= 255 then
558 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
559 local index
= #slice_list
+ 1
560 if fields
.done_edit
then
561 index
= self
.selected
564 slice_list
[index
] = {ypos
= ypos
, prob
= prob
}
566 meta
:set_string("slices", minetest
.serialize(slice_list
))
568 -- Update and show formspec
570 schemedit
.show_formspec(pos
, player
, "slice")
573 if fields
.remove and self
.selected
then
574 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
575 slice_list
[self
.selected
] = nil
576 meta
:set_string("slices", minetest
.serialize(renumber(slice_list
)))
580 self
.panel_edit
= nil
581 schemedit
.show_formspec(pos
, player
, "slice")
585 if not self
.panel_edit
then
586 self
.panel_edit
= true
587 schemedit
.show_formspec(pos
, player
, "slice")
589 self
.panel_edit
= nil
590 schemedit
.show_formspec(pos
, player
, "slice")
596 schemedit
.add_form("probtool", {
598 caption
= S("Schematic Node Probability Tool"),
599 get
= function(self
, pos
, name
)
600 local player
= minetest
.get_player_by_name(name
)
604 local probtool
= player
:get_wielded_item()
605 if probtool
:get_name() ~= "schemedit:probtool" then
609 local meta
= probtool
:get_meta()
610 local prob
= tonumber(meta
:get_string("schemedit_prob"))
611 local force_place
= meta
:get_string("schemedit_force_place")
616 if force_place
== nil or force_place
== "" then
617 force_place
= "false"
619 local form
= "size[5,4]"..
620 "label[0,0;"..F(S("Schematic Node Probability Tool")).."]"..
621 "field[0.75,1;4,1;prob;"..F(S("Probability (0-255)"))..";"..prob
.."]"..
622 "checkbox[0.60,1.5;force_place;"..F(S("Force placement"))..";" .. force_place
.. "]" ..
623 "button_exit[0.25,3;2,1;cancel;"..F(S("Cancel")).."]"..
624 "button_exit[2.75,3;2,1;submit;"..F(S("Apply")).."]"..
625 "tooltip[prob;"..F(S("Probability that the node will be placed")).."]"..
626 "tooltip[force_place;"..F(S("If enabled, the node will replace nodes other than air and ignore")).."]"..
627 "field_close_on_enter[prob;false]"
630 handle
= function(self
, pos
, name
, fields
)
631 if fields
.submit
then
632 local prob
= tonumber(fields
.prob
)
634 local player
= minetest
.get_player_by_name(name
)
638 local probtool
= player
:get_wielded_item()
639 if probtool
:get_name() ~= "schemedit:probtool" then
643 local force_place
= self
.force_place
== true
645 set_item_metadata(probtool
, prob
, force_place
)
647 -- Repurpose the tool's wear bar to display the set probability
648 probtool
:set_wear(math
.floor(((255-prob
)/255)*65535))
650 player
:set_wielded_item(probtool
)
653 if fields
.force_place
== "true" then
654 self
.force_place
= true
655 elseif fields
.force_place
== "false" then
656 self
.force_place
= false
665 --- Copies and modifies positions `pos1` and `pos2` so that each component of
666 -- `pos1` is less than or equal to the corresponding component of `pos2`.
667 -- Returns the new positions.
668 function schemedit
.sort_pos(pos1
, pos2
)
669 if not pos1
or not pos2
then
673 pos1
, pos2
= table.copy(pos1
), table.copy(pos2
)
674 if pos1
.x
> pos2
.x
then
675 pos2
.x
, pos1
.x
= pos1
.x
, pos2
.x
677 if pos1
.y
> pos2
.y
then
678 pos2
.y
, pos1
.y
= pos1
.y
, pos2
.y
680 if pos1
.z
> pos2
.z
then
681 pos2
.z
, pos1
.z
= pos1
.z
, pos2
.z
686 -- [function] Prepare size
687 function schemedit
.size(pos
)
688 local pos1
= vector
.new(pos
)
689 local meta
= minetest
.get_meta(pos
)
690 local node
= minetest
.get_node(pos
)
691 local param2
= node
.param2
693 x
= meta
:get_int("x_size"),
694 y
= math
.max(meta
:get_int("y_size") - 1, 0),
695 z
= meta
:get_int("z_size"),
699 local new_pos
= vector
.add({x
= size
.z
, y
= size
.y
, z
= -size
.x
}, pos
)
701 new_pos
.z
= new_pos
.z
+ 1
703 elseif param2
== 2 then
704 local new_pos
= vector
.add({x
= -size
.x
, y
= size
.y
, z
= -size
.z
}, pos
)
706 new_pos
.x
= new_pos
.x
+ 1
708 elseif param2
== 3 then
709 local new_pos
= vector
.add({x
= -size
.z
, y
= size
.y
, z
= size
.x
}, pos
)
711 new_pos
.z
= new_pos
.z
- 1
714 local new_pos
= vector
.add(size
, pos
)
716 new_pos
.x
= new_pos
.x
- 1
721 -- [function] Mark region
722 function schemedit
.mark(pos
)
723 schemedit
.unmark(pos
)
725 local id
= minetest
.hash_node_position(pos
)
726 local owner
= minetest
.get_meta(pos
):get_string("owner")
727 local pos1
, pos2
= schemedit
.size(pos
)
728 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
730 local thickness
= 0.2
731 local sizex
, sizey
, sizez
= (1 + pos2
.x
- pos1
.x
) / 2, (1 + pos2
.y
- pos1
.y
) / 2, (1 + pos2
.z
- pos1
.z
) / 2
737 for _
, z
in ipairs({pos1
.z
- 0.5, pos2
.z
+ 0.5}) do
743 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= pos1
.y
+ sizey
- 0.5, z
= z
+ offset
}, "schemedit:display")
744 if marker
~= nil then
745 marker
:set_properties({
746 visual_size
={x
=(sizex
+0.01) * 2, y
=(sizey
+0.01) * 2},
748 marker
:get_luaentity().id
= id
749 marker
:get_luaentity().owner
= owner
750 table.insert(m
, marker
)
757 for _
, x
in ipairs({pos1
.x
- 0.5, pos2
.x
+ 0.5}) do
764 local marker
= minetest
.add_entity({x
= x
+ offset
, y
= pos1
.y
+ sizey
- 0.5, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
765 if marker
~= nil then
766 marker
:set_properties({
767 visual_size
={x
=(sizez
+0.01) * 2, y
=(sizey
+0.01) * 2},
769 marker
:set_rotation({x
=0, y
=math
.pi
/ 2, z
=0})
770 marker
:get_luaentity().id
= id
771 marker
:get_luaentity().owner
= owner
772 table.insert(m
, marker
)
779 for _
, y
in ipairs({pos1
.y
- 0.5, pos2
.y
+ 0.5}) do
786 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= y
+ offset
, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
787 if marker
~= nil then
788 marker
:set_properties({
789 visual_size
={x
=(sizex
+0.01) * 2, y
=(sizez
+0.01) * 2},
791 marker
:set_rotation({x
=math
.pi
/2, y
=0, z
=0})
792 marker
:get_luaentity().id
= id
793 marker
:get_luaentity().owner
= owner
794 table.insert(m
, marker
)
801 schemedit
.markers
[id
] = m
805 -- [function] Unmark region
806 function schemedit
.unmark(pos
)
807 local id
= minetest
.hash_node_position(pos
)
808 if schemedit
.markers
[id
] then
810 for _
, entity
in ipairs(schemedit
.markers
[id
]) do
819 --- Mark node probability values near player
822 -- Show probability and force_place status of a particular position for player in HUD.
823 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
824 -- The distance to the node is also displayed below that. This can't be avoided and is
825 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
826 function schemedit
.display_node_prob(player
, pos
, prob
, force_place
)
828 if prob
and force_place
== true then
829 wpstring
= string.format("%s [F]", prob
)
830 elseif prob
and type(tonumber(prob
)) == "number" then
832 elseif force_place
== true then
836 return player
:hud_add({
837 hud_elem_type
= "waypoint",
840 text
= "m", -- For the distance artifact
841 number = text_color_number
,
847 -- Display the node probabilities and force_place status of the nodes in a region.
848 -- By default, this is done for nodes near the player (distance: 5).
849 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
850 function schemedit
.display_node_probs_region(player
, pos1
, pos2
)
851 local playername
= player
:get_player_name()
852 local pos
= vector
.round(player
:get_pos())
855 -- Default: 5 nodes away from player in any direction
857 pos1
= vector
.subtract(pos
, dist
)
860 pos2
= vector
.add(pos
, dist
)
862 for x
=pos1
.x
, pos2
.x
do
863 for y
=pos1
.y
, pos2
.y
do
864 for z
=pos1
.z
, pos2
.z
do
865 local checkpos
= {x
=x
, y
=y
, z
=z
}
866 local nodehash
= minetest
.hash_node_position(checkpos
)
868 -- If node is already displayed, remove it so it can re replaced later
869 if displayed_waypoints
[playername
][nodehash
] then
870 player
:hud_remove(displayed_waypoints
[playername
][nodehash
])
871 displayed_waypoints
[playername
][nodehash
] = nil
874 local prob
, force_place
875 local meta
= minetest
.get_meta(checkpos
)
876 prob
= meta
:get_string("schemedit_prob")
877 force_place
= meta
:get_string("schemedit_force_place") == "true"
878 local hud_id
= schemedit
.display_node_prob(player
, checkpos
, prob
, force_place
)
880 displayed_waypoints
[playername
][nodehash
] = hud_id
881 displayed_waypoints
[playername
].display_active
= true
888 -- Remove all active displayed node statuses.
889 function schemedit
.clear_displayed_node_probs(player
)
890 local playername
= player
:get_player_name()
891 for nodehash
, hud_id
in pairs(displayed_waypoints
[playername
]) do
892 player
:hud_remove(hud_id
)
893 displayed_waypoints
[playername
][nodehash
] = nil
894 displayed_waypoints
[playername
].display_active
= false
898 minetest
.register_on_joinplayer(function(player
)
899 displayed_waypoints
[player
:get_player_name()] = {
900 display_active
= false -- If true, there *might* be at least one active node prob HUD display
901 -- If false, no node probabilities are displayed for sure.
905 minetest
.register_on_leaveplayer(function(player
)
906 displayed_waypoints
[player
:get_player_name()] = nil
909 -- Regularily clear the displayed node probabilities and force_place
910 -- for all players who do not wield the probtool.
911 -- This makes sure the screen is not spammed with information when it
914 minetest
.register_globalstep(function(dtime
)
915 cleartimer
= cleartimer
+ dtime
916 if cleartimer
> 2 then
917 local players
= minetest
.get_connected_players()
918 for p
= 1, #players
do
919 local player
= players
[p
]
920 local pname
= player
:get_player_name()
921 if displayed_waypoints
[pname
].display_active
then
922 local item
= player
:get_wielded_item()
923 if item
:get_name() ~= "schemedit:probtool" then
924 schemedit
.clear_displayed_node_probs(player
)
936 -- [priv] schematic_override
937 minetest
.register_privilege("schematic_override", {
938 description
= S("Allows you to access schemedit nodes not owned by you"),
939 give_to_singleplayer
= false,
942 local help_import
= ""
944 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"
947 -- [node] Schematic creator
948 minetest
.register_node("schemedit:creator", {
949 description
= S("Schematic Creator"),
950 _doc_items_longdesc
= S("The schematic creator is used to save a region of the world into a schematic file (.mts)."),
951 _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"..
952 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"..
954 S("The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.").."\n\n"..
955 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"..
956 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."),
957 tiles
= {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
958 "schemedit_creator_sides.png"},
959 groups
= { dig_immediate
= 2},
960 paramtype2
= "facedir",
961 is_ground_content
= false,
963 after_place_node
= function(pos
, player
)
964 local name
= player
:get_player_name()
965 local meta
= minetest
.get_meta(pos
)
967 meta
:set_string("owner", name
)
968 meta
:set_string("infotext", S("Schematic Creator").."\n"..S("(owned by @1)", name
))
969 meta
:set_string("prob_list", minetest
.serialize({}))
970 meta
:set_string("slices", minetest
.serialize({}))
972 local node
= minetest
.get_node(pos
)
973 local dir
= minetest
.facedir_to_dir(node
.param2
)
975 meta
:set_int("x_size", 1)
976 meta
:set_int("y_size", 1)
977 meta
:set_int("z_size", 1)
979 -- Don't take item from itemstack
982 can_dig
= function(pos
, player
)
983 local name
= player
:get_player_name()
984 local meta
= minetest
.get_meta(pos
)
985 if meta
:get_string("owner") == name
or
986 minetest
.check_player_privs(player
, "schematic_override") == true then
992 on_rightclick
= function(pos
, node
, player
)
993 local meta
= minetest
.get_meta(pos
)
994 local name
= player
:get_player_name()
995 if meta
:get_string("owner") == name
or
996 minetest
.check_player_privs(player
, "schematic_override") == true then
997 -- Get player attribute
998 local tab
= player
:get_attribute("schemedit:tab")
999 if not forms
[tab
] or not tab
then
1003 schemedit
.show_formspec(pos
, player
, tab
, true)
1006 after_destruct
= function(pos
)
1007 schemedit
.unmark(pos
)
1010 -- No support for Minetest Game's screwdriver
1014 minetest
.register_tool("schemedit:probtool", {
1015 description
= S("Schematic Node Probability Tool"),
1016 _doc_items_longdesc
=
1017 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"..
1018 S("It allows you to set two things:").."\n"..
1019 S("1) Set probability: Chance for any particular node to be actually placed (default: always placed)").."\n"..
1020 S("2) Enable force placement: These nodes replace node other than air and ignore when placed in a schematic (default: off)"),
1021 _doc_items_usagehelp
= "\n"..
1022 S("BASIC USAGE:").."\n"..
1023 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"..
1024 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"..
1025 S("NODE HUD:").."\n"..
1026 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"..
1027 S("To disable the node HUD, unselect the tool or hit “place” while not pointing anything.").."\n\n"..
1028 S("UPDATING THE NODE HUD:").."\n"..
1029 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."),
1030 wield_image
= "schemedit_probtool.png",
1031 inventory_image
= "schemedit_probtool.png",
1032 liquids_pointable
= true,
1033 groups
= { disable_repair
= 1 },
1034 on_use
= function(itemstack
, user
, pointed_thing
)
1035 local ctrl
= user
:get_player_control()
1037 if not ctrl
.sneak
then
1038 -- Open dialog to change the probability to apply to nodes
1039 schemedit
.show_formspec(user
:get_pos(), user
, "probtool", true)
1043 -- Display the probability and force_place values for nodes.
1045 -- If a schematic creator was punched, only enable display for all nodes
1046 -- within the creator's region.
1047 local use_creator_region
= false
1048 if pointed_thing
and pointed_thing
.type == "node" and pointed_thing
.under
then
1049 local punchpos
= pointed_thing
.under
1050 local node
= minetest
.get_node(punchpos
)
1051 if node
.name
== "schemedit:creator" then
1052 local pos1
, pos2
= schemedit
.size(punchpos
)
1053 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
1054 schemedit
.display_node_probs_region(user
, pos1
, pos2
)
1059 -- Otherwise, just display the region close to the player
1060 schemedit
.display_node_probs_region(user
)
1063 on_secondary_use
= function(itemstack
, user
, pointed_thing
)
1064 schemedit
.clear_displayed_node_probs(user
)
1066 -- Set note probability and force_place and enable node probability display
1067 on_place
= function(itemstack
, placer
, pointed_thing
)
1068 -- Use pointed node's on_rightclick function first, if present
1069 local node
= minetest
.get_node(pointed_thing
.under
)
1070 if placer
and not placer
:get_player_control().sneak
then
1071 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
1072 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
1076 -- This sets the node probability of pointed node to the
1077 -- currently used probability stored in the tool.
1078 local pos
= pointed_thing
.under
1079 local node
= minetest
.get_node(pos
)
1080 -- Schematic void are ignored, they always have probability 0
1081 if node
.name
== "schemedit:void" then
1084 local nmeta
= minetest
.get_meta(pos
)
1085 local imeta
= itemstack
:get_meta()
1086 local prob
= tonumber(imeta
:get_string("schemedit_prob"))
1087 local force_place
= imeta
:get_string("schemedit_force_place")
1089 if not prob
or prob
== 255 then
1090 nmeta
:set_string("schemedit_prob", nil)
1092 nmeta
:set_string("schemedit_prob", prob
)
1094 if force_place
== "true" then
1095 nmeta
:set_string("schemedit_force_place", "true")
1097 nmeta
:set_string("schemedit_force_place", nil)
1100 -- Enable node probablity display
1101 schemedit
.display_node_probs_region(placer
)
1107 minetest
.register_node("schemedit:void", {
1108 description
= S("Schematic Void"),
1109 _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."),
1110 _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."),
1111 tiles
= { "schemedit_void.png" },
1112 drawtype
= "nodebox",
1113 is_ground_content
= false,
1114 paramtype
= "light",
1116 sunlight_propagates
= true,
1120 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
1123 groups
= { dig_immediate
= 3},
1126 -- [entity] Visible schematic border
1127 minetest
.register_entity("schemedit:display", {
1128 visual
= "upright_sprite",
1129 textures
= {"schemedit_border.png"},
1130 visual_size
= {x
=10, y
=10},
1133 static_save
= false,
1134 glow
= minetest
.LIGHT_MAX
,
1136 on_step
= function(self
, dtime
)
1138 self
.object
:remove()
1139 elseif not schemedit
.markers
[self
.id
] then
1140 self
.object
:remove()
1143 on_activate
= function(self
)
1144 self
.object
:set_armor_groups({immortal
= 1})
1148 minetest
.register_lbm({
1149 label
= "Reset schematic creator border entities",
1150 name
= "schemedit:reset_border",
1151 nodenames
= "schemedit:creator",
1152 run_at_every_load
= true,
1153 action
= function(pos
, node
)
1154 local meta
= minetest
.get_meta(pos
)
1155 meta
:set_string("schem_border", "false")
1159 -- [chatcommand] Place schematic
1160 minetest
.register_chatcommand("placeschem", {
1161 description
= S("Place schematic at the position specified or the current player position (loaded from @1)", export_path_trunc
),
1162 privs
= {debug
= true},
1163 params
= S("<schematic name>[.mts] [<x> <y> <z>]"),
1164 func
= function(name
, param
)
1165 local schem
, p
= string.match(param
, "^([^ ]+) *(.*)$")
1166 local pos
= minetest
.string_to_pos(p
)
1169 return false, S("No schematic file specified.")
1173 pos
= minetest
.get_player_by_name(name
):get_pos()
1176 -- Automatically add file name suffix if omitted
1178 if string.sub(schem
, string.len(schem
)-3, string.len(schem
)) == ".mts" then
1181 schem_full
= schem
.. ".mts"
1184 local success
= false
1185 local schem_path
= export_path_full
.. DIR_DELIM
.. schem_full
1186 if minetest
.read_schematic
then
1187 -- We don't call minetest.place_schematic with the path name directly because
1188 -- this would trigger the caching and we wouldn't get any updates to the schematic
1189 -- files when we reload. minetest.read_schematic circumvents that.
1190 local schematic
= minetest
.read_schematic(schem_path
, {})
1192 success
= minetest
.place_schematic(pos
, schematic
, "random", nil, false)
1195 -- Legacy support for Minetest versions that do not have minetest.read_schematic
1196 success
= minetest
.place_schematic(schem_path
, schematic
, "random", nil, false)
1199 if success
== nil then
1200 return false, S("Schematic file could not be loaded!")