1 local S
= minetest
.get_translator("schemedit")
2 local F
= minetest
.formspec_escape
6 -- Directory delimeter fallback (normally comes from builtin)
10 local export_path_full
= table.concat({minetest
.get_worldpath(), "schems"}, DIR_DELIM
)
12 -- truncated export path so the server directory structure is not exposed publicly
13 local export_path_trunc
= table.concat({S("<world path>"), "schems"}, DIR_DELIM
)
15 local text_color
= "#D79E9E"
16 local text_color_number
= 0xD79E9E
18 schemedit
.markers
= {}
20 -- [local function] Renumber table
21 local function renumber(t
)
23 for _
, i
in pairs(t
) do
37 local displayed_waypoints
= {}
39 -- Sadly, the probabilities presented in Lua (0-255) are not identical to the REAL probabilities in the
40 -- schematic file (0-127). There are two converter functions to convert from one probability type to another.
41 -- This mod tries to retain the “Lua probability” as long as possible and only switches to “schematic probability”
42 -- on an actual export to a schematic.
44 function schemedit
.lua_prob_to_schematic_prob(lua_prob
)
45 return math
.floor(lua_prob
/ 2)
48 function schemedit
.schematic_prob_to_lua_prob(schematic_prob
)
49 return schematic_prob
* 2
53 -- [function] Add form
54 function schemedit
.add_form(name
, def
)
59 tabs
[#tabs
+ 1] = name
63 -- [function] Generate tabs
64 function schemedit
.generate_tabs(current
)
65 local retval
= "tabheader[0,0;tabs;"
66 for _
, t
in pairs(tabs
) do
68 if f
.tab
~= false and f
.caption
then
69 retval
= retval
..f
.caption
..","
71 if type(current
) ~= "number" and current
== f
.name
then
76 retval
= retval
:sub(1, -2) -- Strip last comma
77 retval
= retval
..";"..current
.."]" -- Close tabheader
81 -- [function] Handle tabs
82 function schemedit
.handle_tabs(pos
, name
, fields
)
83 local tab
= tonumber(fields
.tabs
)
84 if tab
and tabs
[tab
] and forms
[tabs
[tab]]
then
85 schemedit
.show_formspec(pos
, name
, forms
[tabs
[tab]]
.name
)
90 -- [function] Show formspec
91 function schemedit
.show_formspec(pos
, player
, tab
, show
, ...)
93 if type(player
) == "string" then
94 player
= minetest
.get_player_by_name(player
)
96 local name
= player
:get_player_name()
99 if not form_data
[name
] then
103 local form
= forms
[tab
].get(form_data
[name
], pos
, name
, ...)
104 if forms
[tab
].tab
then
105 form
= form
..schemedit
.generate_tabs(tab
)
108 minetest
.show_formspec(name
, "schemedit:"..tab
, form
)
111 -- Update player attribute
112 if forms
[tab
].cache_name
~= false then
113 player
:set_attribute("schemedit:tab", tab
)
116 minetest
.close_formspec(pname
, "schemedit:"..tab
)
121 -- [event] On receive fields
122 minetest
.register_on_player_receive_fields(function(player
, formname
, fields
)
123 local formname
= formname
:split(":")
125 if formname
[1] == "schemedit" and forms
[formname
[2]]
then
126 local handle
= forms
[formname
[2]]
.handle
127 local name
= player
:get_player_name()
128 if contexts
[name
] then
129 if not form_data
[name
] then
133 if not schemedit
.handle_tabs(contexts
[name
], name
, fields
) and handle
then
134 handle(form_data
[name
], contexts
[name
], name
, fields
)
140 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
141 schemedit
.scan_metadata
= function(pos1
, pos2
)
144 for x
=pos1
.x
, pos2
.x
do
145 for y
=pos1
.y
, pos2
.y
do
146 for z
=pos1
.z
, pos2
.z
do
147 local scanpos
= {x
=x
, y
=y
, z
=z
}
148 local node
= minetest
.get_node_or_nil(scanpos
)
150 local prob
, force_place
151 if node
== nil or node
.name
== "schemedit:void" then
155 local meta
= minetest
.get_meta(scanpos
)
157 prob
= tonumber(meta
:get_string("schemedit_prob")) or 255
158 local fp
= meta
:get_string("schemedit_force_place")
166 local hashpos
= minetest
.hash_node_position(scanpos
)
167 prob_list
[hashpos
] = {
170 force_place
= force_place
,
179 -- Sets probability and force_place metadata of an item.
180 -- Also updates item description.
181 -- The itemstack is updated in-place.
182 local function set_item_metadata(itemstack
, prob
, force_place
)
183 local smeta
= itemstack
:get_meta()
184 local prob_desc
= "\n"..S("Probability: @1", prob
or
185 smeta
:get_string("schemedit_prob") or S("Not Set"))
186 -- Update probability
187 if prob
and prob
>= 0 and prob
< 255 then
188 smeta
:set_string("schemedit_prob", tostring(prob
))
189 elseif prob
and prob
== 255 then
190 -- Clear prob metadata for default probability
192 smeta
:set_string("schemedit_prob", nil)
194 prob_desc
= "\n"..S("Probability: @1", smeta
:get_string("schemedit_prob") or
198 -- Update force place
199 if force_place
== true then
200 smeta
:set_string("schemedit_force_place", "true")
201 elseif force_place
== false then
202 smeta
:set_string("schemedit_force_place", nil)
205 -- Update description
206 local desc
= minetest
.registered_items
[itemstack
:get_name()].description
207 local meta_desc
= smeta
:get_string("description")
208 if meta_desc
and meta_desc
~= "" then
212 local original_desc
= smeta
:get_string("original_description")
213 if original_desc
and original_desc
~= "" then
216 smeta
:set_string("original_description", desc
)
219 local force_desc
= ""
220 if smeta
:get_string("schemedit_force_place") == "true" then
221 force_desc
= "\n"..S("Force placement")
224 desc
= desc
..minetest
.colorize(text_color
, prob_desc
..force_desc
)
226 smeta
:set_string("description", desc
)
234 local import_btn
= ""
235 if minetest
.read_schematic
then
236 import_btn
= "button[0.5,2.5;6,1;import;"..F(S("Import schematic")).."]"
238 schemedit
.add_form("main", {
241 get
= function(self
, pos
, name
)
242 local meta
= minetest
.get_meta(pos
):to_table().fields
243 local strpos
= minetest
.pos_to_string(pos
)
244 local hashpos
= minetest
.hash_node_position(pos
)
247 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
248 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Hide border")).."]"
250 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Show border")).."]"
253 -- TODO: Show information regarding volume, pos1, pos2, etc... in formspec
256 label[0.5,-0.1;]]..F(S("Position: @1", strpos
))..[[]
257 label[3,-0.1;]]..F(S("Owner: @1", name
))..[[]
259 field[0.8,1;5,1;name;]]..F(S("Schematic name:"))..[[;]]..F(meta
.schem_name
or "")..[[]
260 button[5.3,0.69;1.2,1;save_name;]]..F(S("Save"))..[[]
261 tooltip[save_name;]]..F(S("Save schematic name"))..[[]
262 field_close_on_enter[name;false]
264 button[0.5,1.5;6,1;export;]]..F(S("Export schematic")).."]"..
266 textarea[0.8,3.5;6.2,5;;]]..F(S("The schematic will be exported as a .mts file and stored in\n@1",
267 export_path_trunc
.. DIR_DELIM
.. "<name>.mts."))..[[;]
268 field[0.8,7;2,1;x;]]..F(S("X size:"))..[[;]]..meta
.x_size
..[[]
269 field[2.8,7;2,1;y;]]..F(S("Y size:"))..[[;]]..meta
.y_size
..[[]
270 field[4.8,7;2,1;z;]]..F(S("Z size:"))..[[;]]..meta
.z_size
..[[]
271 field_close_on_enter[x;false]
272 field_close_on_enter[y;false]
273 field_close_on_enter[z;false]
275 button[0.5,7.5;3,1;save;]]..F(S("Save size"))..[[]
278 if minetest
.get_modpath("doc") then
279 form
= form
.. "image_button[6.4,-0.2;0.8,0.8;doc_button_icon_lores.png;doc;]" ..
280 "tooltip[doc;"..F(S("Help")).."]"
284 handle
= function(self
, pos
, name
, fields
)
285 local realmeta
= minetest
.get_meta(pos
)
286 local meta
= realmeta
:to_table().fields
287 local hashpos
= minetest
.hash_node_position(pos
)
290 doc
.show_entry(name
, "nodes", "schemedit:creator", true)
295 if fields
.border
then
296 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
297 schemedit
.unmark(pos
)
298 meta
.schem_border
= "false"
301 meta
.schem_border
= "true"
305 -- Save size vector values
306 if (fields
.save
or fields
.key_enter_field
== "x" or
307 fields
.key_enter_field
== "y" or fields
.key_enter_field
== "z")
308 and (fields
.x
and fields
.y
and fields
.z
and fields
.x
~= ""
309 and fields
.y
~= "" and fields
.z
~= "") then
310 local x
, y
, z
= tonumber(fields
.x
), tonumber(fields
.y
), tonumber(fields
.z
)
313 meta
.x_size
= math
.max(x
, 1)
316 meta
.y_size
= math
.max(y
, 1)
319 meta
.z_size
= math
.max(z
, 1)
323 -- Save schematic name
324 if fields
.save_name
or fields
.key_enter_field
== "name" and fields
.name
and
325 fields
.name
~= "" then
326 meta
.schem_name
= fields
.name
330 if fields
.export
and meta
.schem_name
and meta
.schem_name
~= "" then
331 local pos1
, pos2
= schemedit
.size(pos
)
332 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
333 local path
= export_path_full
.. DIR_DELIM
336 local plist
= schemedit
.scan_metadata(pos1
, pos2
)
337 local probability_list
= {}
338 for hash
, i
in pairs(plist
) do
339 local prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
)
340 if i
.force_place
== true then
344 table.insert(probability_list
, {
345 pos
= minetest
.get_position_from_hash(hash
),
350 local slist
= minetest
.deserialize(meta
.slices
)
351 local slice_list
= {}
352 for _
, i
in pairs(slist
) do
353 slice_list
[#slice_list
+ 1] = {
354 ypos
= pos
.y
+ i
.ypos
,
355 prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
),
359 local filepath
= path
..meta
.schem_name
..".mts"
360 local res
= minetest
.create_schematic(pos1
, pos2
, probability_list
, filepath
, slice_list
)
363 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
364 S("Exported schematic to @1", filepath
)))
366 minetest
.chat_send_player(name
, minetest
.colorize("red",
367 S("Failed to export schematic to @1", filepath
)))
372 if fields
.import
and meta
.schem_name
and meta
.schem_name
~= "" then
373 if not minetest
.read_schematic
then
377 local node
= minetest
.get_node(pos
)
378 local path
= export_path_full
.. DIR_DELIM
380 local filepath
= path
..meta
.schem_name
..".mts"
381 local schematic
= minetest
.read_schematic(filepath
, {write_yslice_prob
="low"})
382 local success
= false
385 meta
.x_size
= schematic
.size
.x
386 meta
.y_size
= schematic
.size
.y
387 meta
.z_size
= schematic
.size
.z
388 meta
.slices
= minetest
.serialize(schematic
.yslice_prob
)
390 if node
.param2
== 1 then
391 pos1
= vector
.add(pos
, {x
=1,y
=0,z
=-meta
.z_size
+1})
392 elseif node
.param2
== 2 then
393 pos1
= vector
.add(pos
, {x
=-meta
.x_size
+1,y
=0,z
=-meta
.z_size
})
394 elseif node
.param2
== 3 then
395 pos1
= vector
.add(pos
, {x
=-meta
.x_size
,y
=0,z
=0})
397 pos1
= vector
.add(pos
, {x
=0,y
=0,z
=1})
399 schematic
.yslice_prob
= {}
401 success
= minetest
.place_schematic(pos1
, schematic
, "0", nil, true)
404 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
405 S("Imported schematic from @1", filepath
)))
407 minetest
.chat_send_player(name
, minetest
.colorize("red",
408 S("Failed to import schematic from @1", filepath
)))
414 -- Save meta before updating visuals
415 local inv
= realmeta
:get_inventory():get_lists()
416 realmeta
:from_table({fields
= meta
, inventory
= inv
})
419 if not fields
.border
and meta
.schem_border
== "true" then
424 if not fields
.quit
then
425 schemedit
.show_formspec(pos
, minetest
.get_player_by_name(name
), "main")
430 schemedit
.add_form("slice", {
431 caption
= S("Y Slices"),
433 get
= function(self
, pos
, name
, visible_panel
)
434 local meta
= minetest
.get_meta(pos
):to_table().fields
436 self
.selected
= self
.selected
or 1
437 local selected
= tostring(self
.selected
)
438 local slice_list
= minetest
.deserialize(meta
.slices
)
440 for _
, i
in pairs(slice_list
) do
441 local insert
= F(S("Y = @1; Probability = @2", tostring(i
.ypos
), tostring(i
.prob
)))
442 slices
= slices
..insert
..","
444 slices
= slices
:sub(1, -2) -- Remove final comma
448 table[0,0;6.8,6;slices;]]..slices
..[[;]]..selected
..[[]
451 if self
.panel_add
or self
.panel_edit
then
452 local ypos_default
, prob_default
= "", ""
453 local done_button
= "button[5,7.18;2,1;done_add;"..F(S("Done")).."]"
454 if self
.panel_edit
then
455 done_button
= "button[5,7.18;2,1;done_edit;"..F(S("Done")).."]"
456 if slice_list
[self
.selected
] then
457 ypos_default
= slice_list
[self
.selected
].ypos
458 prob_default
= slice_list
[self
.selected
].prob
463 field[0.3,7.5;2.5,1;ypos;]]..F(S("Y position (max. @1):", (meta
.y_size
- 1)))..[[;]]..ypos_default
..[[]
464 field[2.8,7.5;2.5,1;prob;]]..F(S("Probability (0-255):"))..[[;]]..prob_default
..[[]
465 field_close_on_enter[ypos;false]
466 field_close_on_enter[prob;false]
470 if not self
.panel_edit
then
471 form
= form
.."button[0,6;2,1;add;"..F(S("+ Add slice")).."]"
474 if slices
~= "" and self
.selected
and not self
.panel_add
then
475 if not self
.panel_edit
then
477 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
478 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
482 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
483 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
490 handle
= function(self
, pos
, name
, fields
)
491 local meta
= minetest
.get_meta(pos
)
492 local player
= minetest
.get_player_by_name(name
)
494 if fields
.slices
then
495 local slices
= fields
.slices
:split(":")
496 self
.selected
= tonumber(slices
[2])
500 if not self
.panel_add
then
501 self
.panel_add
= true
502 schemedit
.show_formspec(pos
, player
, "slice")
505 schemedit
.show_formspec(pos
, player
, "slice")
509 local ypos
, prob
= tonumber(fields
.ypos
), tonumber(fields
.prob
)
510 if (fields
.done_add
or fields
.done_edit
) and fields
.ypos
and fields
.prob
and
511 fields
.ypos
~= "" and fields
.prob
~= "" and ypos
and prob
and
512 ypos
<= (meta
:get_int("y_size") - 1) and prob
>= 0 and prob
<= 255 then
513 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
514 local index
= #slice_list
+ 1
515 if fields
.done_edit
then
516 index
= self
.selected
519 slice_list
[index
] = {ypos
= ypos
, prob
= prob
}
521 meta
:set_string("slices", minetest
.serialize(slice_list
))
523 -- Update and show formspec
525 schemedit
.show_formspec(pos
, player
, "slice")
528 if fields
.remove and self
.selected
then
529 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
530 slice_list
[self
.selected
] = nil
531 meta
:set_string("slices", minetest
.serialize(renumber(slice_list
)))
535 self
.panel_edit
= nil
536 schemedit
.show_formspec(pos
, player
, "slice")
540 if not self
.panel_edit
then
541 self
.panel_edit
= true
542 schemedit
.show_formspec(pos
, player
, "slice")
544 self
.panel_edit
= nil
545 schemedit
.show_formspec(pos
, player
, "slice")
551 schemedit
.add_form("probtool", {
553 caption
= S("Schematic Node Probability Tool"),
554 get
= function(self
, pos
, name
)
555 local player
= minetest
.get_player_by_name(name
)
559 local probtool
= player
:get_wielded_item()
560 if probtool
:get_name() ~= "schemedit:probtool" then
564 local meta
= probtool
:get_meta()
565 local prob
= tonumber(meta
:get_string("schemedit_prob"))
566 local force_place
= meta
:get_string("schemedit_force_place")
571 if force_place
== nil or force_place
== "" then
572 force_place
= "false"
574 local form
= "size[5,4]"..
575 "label[0,0;"..F(S("Schematic Node Probability Tool")).."]"..
576 "field[0.75,1;4,1;prob;"..F(S("Probability (0-255)"))..";"..prob
.."]"..
577 "checkbox[0.60,1.5;force_place;"..F(S("Force placement"))..";" .. force_place
.. "]" ..
578 "button_exit[0.25,3;2,1;cancel;"..F(S("Cancel")).."]"..
579 "button_exit[2.75,3;2,1;submit;"..F(S("Apply")).."]"..
580 "tooltip[prob;"..F(S("Probability that the node will be placed")).."]"..
581 "tooltip[force_place;"..F(S("If enabled, the node will replace nodes other than air and ignore")).."]"..
582 "field_close_on_enter[prob;false]"
585 handle
= function(self
, pos
, name
, fields
)
586 if fields
.submit
then
587 local prob
= tonumber(fields
.prob
)
589 local player
= minetest
.get_player_by_name(name
)
593 local probtool
= player
:get_wielded_item()
594 if probtool
:get_name() ~= "schemedit:probtool" then
598 local force_place
= self
.force_place
== true
600 set_item_metadata(probtool
, prob
, force_place
)
602 -- Repurpose the tool's wear bar to display the set probability
603 probtool
:set_wear(math
.floor(((255-prob
)/255)*65535))
605 player
:set_wielded_item(probtool
)
608 if fields
.force_place
== "true" then
609 self
.force_place
= true
610 elseif fields
.force_place
== "false" then
611 self
.force_place
= false
620 --- Copies and modifies positions `pos1` and `pos2` so that each component of
621 -- `pos1` is less than or equal to the corresponding component of `pos2`.
622 -- Returns the new positions.
623 function schemedit
.sort_pos(pos1
, pos2
)
624 if not pos1
or not pos2
then
628 pos1
, pos2
= table.copy(pos1
), table.copy(pos2
)
629 if pos1
.x
> pos2
.x
then
630 pos2
.x
, pos1
.x
= pos1
.x
, pos2
.x
632 if pos1
.y
> pos2
.y
then
633 pos2
.y
, pos1
.y
= pos1
.y
, pos2
.y
635 if pos1
.z
> pos2
.z
then
636 pos2
.z
, pos1
.z
= pos1
.z
, pos2
.z
641 -- [function] Prepare size
642 function schemedit
.size(pos
)
643 local pos1
= vector
.new(pos
)
644 local meta
= minetest
.get_meta(pos
)
645 local node
= minetest
.get_node(pos
)
646 local param2
= node
.param2
648 x
= meta
:get_int("x_size"),
649 y
= math
.max(meta
:get_int("y_size") - 1, 0),
650 z
= meta
:get_int("z_size"),
654 local new_pos
= vector
.add({x
= size
.z
, y
= size
.y
, z
= -size
.x
}, pos
)
656 new_pos
.z
= new_pos
.z
+ 1
658 elseif param2
== 2 then
659 local new_pos
= vector
.add({x
= -size
.x
, y
= size
.y
, z
= -size
.z
}, pos
)
661 new_pos
.x
= new_pos
.x
+ 1
663 elseif param2
== 3 then
664 local new_pos
= vector
.add({x
= -size
.z
, y
= size
.y
, z
= size
.x
}, pos
)
666 new_pos
.z
= new_pos
.z
- 1
669 local new_pos
= vector
.add(size
, pos
)
671 new_pos
.x
= new_pos
.x
- 1
676 -- [function] Mark region
677 function schemedit
.mark(pos
)
678 schemedit
.unmark(pos
)
680 local id
= minetest
.hash_node_position(pos
)
681 local owner
= minetest
.get_meta(pos
):get_string("owner")
682 local pos1
, pos2
= schemedit
.size(pos
)
683 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
685 local thickness
= 0.2
686 local sizex
, sizey
, sizez
= (1 + pos2
.x
- pos1
.x
) / 2, (1 + pos2
.y
- pos1
.y
) / 2, (1 + pos2
.z
- pos1
.z
) / 2
692 for _
, z
in ipairs({pos1
.z
- 0.5, pos2
.z
+ 0.5}) do
698 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= pos1
.y
+ sizey
- 0.5, z
= z
+ offset
}, "schemedit:display")
699 if marker
~= nil then
700 marker
:set_properties({
701 visual_size
={x
=(sizex
+0.01) * 2, y
=sizey
* 2},
703 marker
:get_luaentity().id
= id
704 marker
:get_luaentity().owner
= owner
705 table.insert(m
, marker
)
712 for _
, x
in ipairs({pos1
.x
- 0.5, pos2
.x
+ 0.5}) do
719 local marker
= minetest
.add_entity({x
= x
+ offset
, y
= pos1
.y
+ sizey
- 0.5, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
720 if marker
~= nil then
721 marker
:set_properties({
722 visual_size
={x
=(sizez
+0.01) * 2, y
=sizey
* 2},
724 marker
:set_yaw(math
.pi
/ 2)
725 marker
:get_luaentity().id
= id
726 marker
:get_luaentity().owner
= owner
727 table.insert(m
, marker
)
732 schemedit
.markers
[id
] = m
736 -- [function] Unmark region
737 function schemedit
.unmark(pos
)
738 local id
= minetest
.hash_node_position(pos
)
739 if schemedit
.markers
[id
] then
741 for _
, entity
in ipairs(schemedit
.markers
[id
]) do
750 --- Mark node probability values near player
753 -- Show probability and force_place status of a particular position for player in HUD.
754 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
755 -- The distance to the node is also displayed below that. This can't be avoided and is
756 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
757 function schemedit
.display_node_prob(player
, pos
, prob
, force_place
)
759 if prob
and force_place
== true then
760 wpstring
= string.format("%d [F]", prob
)
763 elseif force_place
== true then
767 return player
:hud_add({
768 hud_elem_type
= "waypoint",
770 text
= "m", -- For the distance artifact
771 number = text_color_number
,
777 -- Display the node probabilities and force_place status of the nodes in a region.
778 -- By default, this is done for nodes near the player (distance: 5).
779 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
780 function schemedit
.display_node_probs_region(player
, pos1
, pos2
)
781 local playername
= player
:get_player_name()
782 local pos
= vector
.round(player
:get_pos())
785 -- Default: 5 nodes away from player in any direction
787 pos1
= vector
.subtract(pos
, dist
)
790 pos2
= vector
.add(pos
, dist
)
792 for x
=pos1
.x
, pos2
.x
do
793 for y
=pos1
.y
, pos2
.y
do
794 for z
=pos1
.z
, pos2
.z
do
795 local checkpos
= {x
=x
, y
=y
, z
=z
}
796 local nodehash
= minetest
.hash_node_position(checkpos
)
798 -- If node is already displayed, remove it so it can re replaced later
799 if displayed_waypoints
[playername
][nodehash
] then
800 player
:hud_remove(displayed_waypoints
[playername
][nodehash
])
801 displayed_waypoints
[playername
][nodehash
] = nil
804 local prob
, force_place
805 local meta
= minetest
.get_meta(checkpos
)
806 prob
= tonumber(meta
:get_string("schemedit_prob"))
807 force_place
= meta
:get_string("schemedit_force_place") == "true"
808 local hud_id
= schemedit
.display_node_prob(player
, checkpos
, prob
, force_place
)
810 displayed_waypoints
[playername
][nodehash
] = hud_id
811 displayed_waypoints
[playername
].display_active
= true
818 -- Remove all active displayed node statuses.
819 function schemedit
.clear_displayed_node_probs(player
)
820 local playername
= player
:get_player_name()
821 for nodehash
, hud_id
in pairs(displayed_waypoints
[playername
]) do
822 player
:hud_remove(hud_id
)
823 displayed_waypoints
[playername
][nodehash
] = nil
824 displayed_waypoints
[playername
].display_active
= false
828 minetest
.register_on_joinplayer(function(player
)
829 displayed_waypoints
[player
:get_player_name()] = {
830 display_active
= false -- If true, there *might* be at least one active node prob HUD display
831 -- If false, no node probabilities are displayed for sure.
835 minetest
.register_on_leaveplayer(function(player
)
836 displayed_waypoints
[player
:get_player_name()] = nil
839 -- Regularily clear the displayed node probabilities and force_place
840 -- for all players who do not wield the probtool.
841 -- This makes sure the screen is not spammed with information when it
844 minetest
.register_globalstep(function(dtime
)
845 cleartimer
= cleartimer
+ dtime
846 if cleartimer
> 2 then
847 local players
= minetest
.get_connected_players()
848 for p
= 1, #players
do
849 local player
= players
[p
]
850 local pname
= player
:get_player_name()
851 if displayed_waypoints
[pname
].display_active
then
852 local item
= player
:get_wielded_item()
853 if item
:get_name() ~= "schemedit:probtool" then
854 schemedit
.clear_displayed_node_probs(player
)
866 -- [priv] schematic_override
867 minetest
.register_privilege("schematic_override", {
868 description
= S("Allows you to access schemedit nodes not owned by you"),
869 give_to_singleplayer
= false,
872 -- [node] Schematic creator
873 minetest
.register_node("schemedit:creator", {
874 description
= S("Schematic Creator"),
875 _doc_items_longdesc
= S("The schematic creator is used to save a region of the world into a schematic file (.mts)."),
876 _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"..
877 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"..
878 S("The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.").."\n\n"..
879 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"..
880 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."),
881 tiles
= {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
882 "schemedit_creator_sides.png"},
883 groups
= { dig_immediate
= 2},
884 paramtype2
= "facedir",
885 is_ground_content
= false,
887 after_place_node
= function(pos
, player
)
888 local name
= player
:get_player_name()
889 local meta
= minetest
.get_meta(pos
)
891 meta
:set_string("owner", name
)
892 meta
:set_string("infotext", S("Schematic Creator").."\n"..S("(owned by @1)", name
))
893 meta
:set_string("prob_list", minetest
.serialize({}))
894 meta
:set_string("slices", minetest
.serialize({}))
896 local node
= minetest
.get_node(pos
)
897 local dir
= minetest
.facedir_to_dir(node
.param2
)
899 meta
:set_int("x_size", 1)
900 meta
:set_int("y_size", 1)
901 meta
:set_int("z_size", 1)
903 -- Don't take item from itemstack
906 can_dig
= function(pos
, player
)
907 local name
= player
:get_player_name()
908 local meta
= minetest
.get_meta(pos
)
909 if meta
:get_string("owner") == name
or
910 minetest
.check_player_privs(player
, "schematic_override") == true then
916 on_rightclick
= function(pos
, node
, player
)
917 local meta
= minetest
.get_meta(pos
)
918 local name
= player
:get_player_name()
919 if meta
:get_string("owner") == name
or
920 minetest
.check_player_privs(player
, "schematic_override") == true then
921 -- Get player attribute
922 local tab
= player
:get_attribute("schemedit:tab")
923 if not forms
[tab
] or not tab
then
927 schemedit
.show_formspec(pos
, player
, tab
, true)
930 after_destruct
= function(pos
)
931 schemedit
.unmark(pos
)
935 minetest
.register_tool("schemedit:probtool", {
936 description
= S("Schematic Node Probability Tool"),
937 _doc_items_longdesc
=
938 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"..
939 S("It allows you to set two things:").."\n"..
940 S("1) Set probability: Chance for any particular node to be actually placed (default: always placed)").."\n"..
941 S("2) Enable force placement: These nodes replace node other than air and ignore when placed in a schematic (default: off)"),
942 _doc_items_usagehelp
= "\n"..
943 S("BASIC USAGE:").."\n"..
944 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"..
945 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"..
946 S("NODE HUD:").."\n"..
947 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"..
948 S("To disable the node HUD, unselect the tool or hit “place” while not pointing anything.").."\n\n"..
949 S("UPDATING THE NODE HUD:").."\n"..
950 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."),
951 wield_image
= "schemedit_probtool.png",
952 inventory_image
= "schemedit_probtool.png",
953 liquids_pointable
= true,
954 groups
= { disable_repair
= 1 },
955 on_use
= function(itemstack
, user
, pointed_thing
)
956 local ctrl
= user
:get_player_control()
958 if not ctrl
.sneak
then
959 -- Open dialog to change the probability to apply to nodes
960 schemedit
.show_formspec(user
:get_pos(), user
, "probtool", true)
964 -- Display the probability and force_place values for nodes.
966 -- If a schematic creator was punched, only enable display for all nodes
967 -- within the creator's region.
968 local use_creator_region
= false
969 if pointed_thing
and pointed_thing
.type == "node" and pointed_thing
.under
then
970 punchpos
= pointed_thing
.under
971 local node
= minetest
.get_node(punchpos
)
972 if node
.name
== "schemedit:creator" then
973 local pos1
, pos2
= schemedit
.size(punchpos
)
974 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
975 schemedit
.display_node_probs_region(user
, pos1
, pos2
)
980 -- Otherwise, just display the region close to the player
981 schemedit
.display_node_probs_region(user
)
984 on_secondary_use
= function(itemstack
, user
, pointed_thing
)
985 schemedit
.clear_displayed_node_probs(user
)
987 -- Set note probability and force_place and enable node probability display
988 on_place
= function(itemstack
, placer
, pointed_thing
)
989 -- Use pointed node's on_rightclick function first, if present
990 local node
= minetest
.get_node(pointed_thing
.under
)
991 if placer
and not placer
:get_player_control().sneak
then
992 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
993 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
997 -- This sets the node probability of pointed node to the
998 -- currently used probability stored in the tool.
999 local pos
= pointed_thing
.under
1000 local node
= minetest
.get_node(pos
)
1001 -- Schematic void are ignored, they always have probability 0
1002 if node
.name
== "schemedit:void" then
1005 local nmeta
= minetest
.get_meta(pos
)
1006 local imeta
= itemstack
:get_meta()
1007 local prob
= tonumber(imeta
:get_string("schemedit_prob"))
1008 local force_place
= imeta
:get_string("schemedit_force_place")
1010 if not prob
or prob
== 255 then
1011 nmeta
:set_string("schemedit_prob", nil)
1013 nmeta
:set_string("schemedit_prob", prob
)
1015 if force_place
== "true" then
1016 nmeta
:set_string("schemedit_force_place", "true")
1018 nmeta
:set_string("schemedit_force_place", nil)
1021 -- Enable node probablity display
1022 schemedit
.display_node_probs_region(placer
)
1028 minetest
.register_node("schemedit:void", {
1029 description
= S("Schematic Void"),
1030 _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."),
1031 _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."),
1032 tiles
= { "schemedit_void.png" },
1033 drawtype
= "nodebox",
1034 is_ground_content
= false,
1035 paramtype
= "light",
1037 sunlight_propagates
= true,
1041 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
1044 groups
= { dig_immediate
= 3},
1047 -- [entity] Visible schematic border
1048 minetest
.register_entity("schemedit:display", {
1049 visual
= "upright_sprite",
1050 textures
= {"schemedit_border.png"},
1051 visual_size
= {x
=10, y
=10},
1054 static_save
= false,
1055 glow
= minetest
.LIGHT_MAX
,
1057 on_step
= function(self
, dtime
)
1059 self
.object
:remove()
1060 elseif not schemedit
.markers
[self
.id
] then
1061 self
.object
:remove()
1064 on_activate
= function(self
)
1065 self
.object
:set_armor_groups({immortal
= 1})
1069 minetest
.register_lbm({
1070 label
= "Reset schematic creator border entities",
1071 name
= "schemedit:reset_border",
1072 nodenames
= "schemedit:creator",
1073 run_at_every_load
= true,
1074 action
= function(pos
, node
)
1075 local meta
= minetest
.get_meta(pos
)
1076 meta
:set_string("schem_border", "false")
1080 -- [chatcommand] Place schematic
1081 minetest
.register_chatcommand("placeschem", {
1082 description
= S("Place schematic at the position specified or the current player position (loaded from @1)", export_path_trunc
),
1083 privs
= {debug
= true},
1084 params
= S("<schematic name>[.mts] [<x> <y> <z>]"),
1085 func
= function(name
, param
)
1086 local schem
, p
= string.match(param
, "^([^ ]+) *(.*)$")
1087 local pos
= minetest
.string_to_pos(p
)
1090 return false, S("No schematic file specified.")
1094 pos
= minetest
.get_player_by_name(name
):get_pos()
1097 -- Automatically add file name suffix if omitted
1099 if string.sub(schem
, string.len(schem
)-3, string.len(schem
)) == ".mts" then
1102 schem_full
= schem
.. ".mts"
1105 local success
= false
1106 local schem_path
= export_path_full
.. DIR_DELIM
.. schem_full
1107 if minetest
.read_schematic
then
1108 -- We don't call minetest.place_schematic with the path name directly because
1109 -- this would trigger the caching and we wouldn't get any updates to the schematic
1110 -- files when we reload. minetest.read_schematic circumvents that.
1111 local schematic
= minetest
.read_schematic(schem_path
, {})
1113 success
= minetest
.place_schematic(pos
, schematic
, "random", nil, false)
1116 -- Legacy support for Minetest versions that do not have minetest.read_schematic
1117 success
= minetest
.place_schematic(schem_path
, schematic
, "random", nil, false)
1120 if success
== nil then
1121 return false, S("Schematic file could not be loaded!")