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
)
235 schemedit
.add_form("main", {
238 get
= function(self
, pos
, name
)
239 local meta
= minetest
.get_meta(pos
):to_table().fields
240 local strpos
= minetest
.pos_to_string(pos
)
241 local hashpos
= minetest
.hash_node_position(pos
)
244 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
245 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Hide border")).."]"
247 border_button
= "button[3.5,7.5;3,1;border;"..F(S("Show border")).."]"
250 -- TODO: Show information regarding volume, pos1, pos2, etc... in formspec
253 label[0.5,-0.1;]]..F(S("Position: @1", strpos
))..[[]
254 label[3,-0.1;]]..F(S("Owner: @1", name
))..[[]
256 field[0.8,1;5,1;name;]]..F(S("Schematic name:"))..[[;]]..F(meta
.schem_name
or "")..[[]
257 button[5.3,0.69;1.2,1;save_name;]]..F(S("Save"))..[[]
258 tooltip[save_name;]]..F(S("Save schematic name"))..[[]
259 field_close_on_enter[name;false]
261 button[0.5,1.5;6,1;export;]]..F(S("Export schematic"))..[[]
262 textarea[0.8,2.5;6.2,5;;]]..F(S("The schematic will be exported as a .mts file and stored in\n@1",
263 export_path_trunc
.. DIR_DELIM
.. "<name>.mts."))..[[;]
264 field[0.8,7;2,1;x;]]..F(S("X size:"))..[[;]]..meta
.x_size
..[[]
265 field[2.8,7;2,1;y;]]..F(S("Y size:"))..[[;]]..meta
.y_size
..[[]
266 field[4.8,7;2,1;z;]]..F(S("Z size:"))..[[;]]..meta
.z_size
..[[]
267 field_close_on_enter[x;false]
268 field_close_on_enter[y;false]
269 field_close_on_enter[z;false]
271 button[0.5,7.5;3,1;save;]]..F(S("Save size"))..[[]
274 if minetest
.get_modpath("doc") then
275 form
= form
.. "image_button[6.4,-0.2;0.8,0.8;doc_button_icon_lores.png;doc;]" ..
276 "tooltip[doc;"..F(S("Help")).."]"
280 handle
= function(self
, pos
, name
, fields
)
281 local realmeta
= minetest
.get_meta(pos
)
282 local meta
= realmeta
:to_table().fields
283 local hashpos
= minetest
.hash_node_position(pos
)
286 doc
.show_entry(name
, "nodes", "schemedit:creator", true)
291 if fields
.border
then
292 if meta
.schem_border
== "true" and schemedit
.markers
[hashpos
] then
293 schemedit
.unmark(pos
)
294 meta
.schem_border
= "false"
297 meta
.schem_border
= "true"
301 -- Save size vector values
302 if (fields
.save
or fields
.key_enter_field
== "x" or
303 fields
.key_enter_field
== "y" or fields
.key_enter_field
== "z")
304 and (fields
.x
and fields
.y
and fields
.z
and fields
.x
~= ""
305 and fields
.y
~= "" and fields
.z
~= "") then
306 local x
, y
, z
= tonumber(fields
.x
), tonumber(fields
.y
), tonumber(fields
.z
)
309 meta
.x_size
= math
.max(x
, 1)
312 meta
.y_size
= math
.max(y
, 1)
315 meta
.z_size
= math
.max(z
, 1)
319 -- Save schematic name
320 if fields
.save_name
or fields
.key_enter_field
== "name" and fields
.name
and
321 fields
.name
~= "" then
322 meta
.schem_name
= fields
.name
326 if fields
.export
and meta
.schem_name
and meta
.schem_name
~= "" then
327 local pos1
, pos2
= schemedit
.size(pos
)
328 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
329 local path
= export_path_full
.. DIR_DELIM
332 local plist
= schemedit
.scan_metadata(pos1
, pos2
)
333 local probability_list
= {}
334 for hash
, i
in pairs(plist
) do
335 local prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
)
336 if i
.force_place
== true then
340 table.insert(probability_list
, {
341 pos
= minetest
.get_position_from_hash(hash
),
346 local slist
= minetest
.deserialize(meta
.slices
)
347 local slice_list
= {}
348 for _
, i
in pairs(slist
) do
349 slice_list
[#slice_list
+ 1] = {
350 ypos
= pos
.y
+ i
.ypos
,
351 prob
= schemedit
.lua_prob_to_schematic_prob(i
.prob
),
355 local filepath
= path
..meta
.schem_name
..".mts"
356 local res
= minetest
.create_schematic(pos1
, pos2
, probability_list
, filepath
, slice_list
)
359 minetest
.chat_send_player(name
, minetest
.colorize("#00ff00",
360 S("Exported schematic to @1", filepath
)))
362 minetest
.chat_send_player(name
, minetest
.colorize("red",
363 S("Failed to export schematic to @1", filepath
)))
367 -- Save meta before updating visuals
368 local inv
= realmeta
:get_inventory():get_lists()
369 realmeta
:from_table({fields
= meta
, inventory
= inv
})
372 if not fields
.border
and meta
.schem_border
== "true" then
377 if not fields
.quit
then
378 schemedit
.show_formspec(pos
, minetest
.get_player_by_name(name
), "main")
383 schemedit
.add_form("slice", {
384 caption
= S("Y Slices"),
386 get
= function(self
, pos
, name
, visible_panel
)
387 local meta
= minetest
.get_meta(pos
):to_table().fields
389 self
.selected
= self
.selected
or 1
390 local selected
= tostring(self
.selected
)
391 local slice_list
= minetest
.deserialize(meta
.slices
)
393 for _
, i
in pairs(slice_list
) do
394 local insert
= F(S("Y = @1; Probability = @2", tostring(i
.ypos
), tostring(i
.prob
)))
395 slices
= slices
..insert
..","
397 slices
= slices
:sub(1, -2) -- Remove final comma
401 table[0,0;6.8,6;slices;]]..slices
..[[;]]..selected
..[[]
404 if self
.panel_add
or self
.panel_edit
then
405 local ypos_default
, prob_default
= "", ""
406 local done_button
= "button[5,7.18;2,1;done_add;"..F(S("Done")).."]"
407 if self
.panel_edit
then
408 done_button
= "button[5,7.18;2,1;done_edit;"..F(S("Done")).."]"
409 if slice_list
[self
.selected
] then
410 ypos_default
= slice_list
[self
.selected
].ypos
411 prob_default
= slice_list
[self
.selected
].prob
416 field[0.3,7.5;2.5,1;ypos;]]..F(S("Y position (max. @1):", (meta
.y_size
- 1)))..[[;]]..ypos_default
..[[]
417 field[2.8,7.5;2.5,1;prob;]]..F(S("Probability (0-255):"))..[[;]]..prob_default
..[[]
418 field_close_on_enter[ypos;false]
419 field_close_on_enter[prob;false]
423 if not self
.panel_edit
then
424 form
= form
.."button[0,6;2,1;add;"..F(S("+ Add slice")).."]"
427 if slices
~= "" and self
.selected
and not self
.panel_add
then
428 if not self
.panel_edit
then
430 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
431 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
435 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
436 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
443 handle
= function(self
, pos
, name
, fields
)
444 local meta
= minetest
.get_meta(pos
)
445 local player
= minetest
.get_player_by_name(name
)
447 if fields
.slices
then
448 local slices
= fields
.slices
:split(":")
449 self
.selected
= tonumber(slices
[2])
453 if not self
.panel_add
then
454 self
.panel_add
= true
455 schemedit
.show_formspec(pos
, player
, "slice")
458 schemedit
.show_formspec(pos
, player
, "slice")
462 local ypos
, prob
= tonumber(fields
.ypos
), tonumber(fields
.prob
)
463 if (fields
.done_add
or fields
.done_edit
) and fields
.ypos
and fields
.prob
and
464 fields
.ypos
~= "" and fields
.prob
~= "" and ypos
and prob
and
465 ypos
<= (meta
:get_int("y_size") - 1) and prob
>= 0 and prob
<= 255 then
466 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
467 local index
= #slice_list
+ 1
468 if fields
.done_edit
then
469 index
= self
.selected
472 slice_list
[index
] = {ypos
= ypos
, prob
= prob
}
474 meta
:set_string("slices", minetest
.serialize(slice_list
))
476 -- Update and show formspec
478 schemedit
.show_formspec(pos
, player
, "slice")
481 if fields
.remove and self
.selected
then
482 local slice_list
= minetest
.deserialize(meta
:get_string("slices"))
483 slice_list
[self
.selected
] = nil
484 meta
:set_string("slices", minetest
.serialize(renumber(slice_list
)))
488 self
.panel_edit
= nil
489 schemedit
.show_formspec(pos
, player
, "slice")
493 if not self
.panel_edit
then
494 self
.panel_edit
= true
495 schemedit
.show_formspec(pos
, player
, "slice")
497 self
.panel_edit
= nil
498 schemedit
.show_formspec(pos
, player
, "slice")
504 schemedit
.add_form("probtool", {
506 caption
= S("Schematic Node Probability Tool"),
507 get
= function(self
, pos
, name
)
508 local player
= minetest
.get_player_by_name(name
)
512 local probtool
= player
:get_wielded_item()
513 if probtool
:get_name() ~= "schemedit:probtool" then
517 local meta
= probtool
:get_meta()
518 local prob
= tonumber(meta
:get_string("schemedit_prob"))
519 local force_place
= meta
:get_string("schemedit_force_place")
524 if force_place
== nil or force_place
== "" then
525 force_place
= "false"
527 local form
= "size[5,4]"..
528 "label[0,0;"..F(S("Schematic Node Probability Tool")).."]"..
529 "field[0.75,1;4,1;prob;"..F(S("Probability (0-255)"))..";"..prob
.."]"..
530 "checkbox[0.60,1.5;force_place;"..F(S("Force placement"))..";" .. force_place
.. "]" ..
531 "button_exit[0.25,3;2,1;cancel;"..F(S("Cancel")).."]"..
532 "button_exit[2.75,3;2,1;submit;"..F(S("Apply")).."]"..
533 "tooltip[prob;"..F(S("Probability that the node will be placed")).."]"..
534 "tooltip[force_place;"..F(S("If enabled, the node will replace nodes other than air and ignore")).."]"..
535 "field_close_on_enter[prob;false]"
538 handle
= function(self
, pos
, name
, fields
)
539 if fields
.submit
then
540 local prob
= tonumber(fields
.prob
)
542 local player
= minetest
.get_player_by_name(name
)
546 local probtool
= player
:get_wielded_item()
547 if probtool
:get_name() ~= "schemedit:probtool" then
551 local force_place
= self
.force_place
== true
553 set_item_metadata(probtool
, prob
, force_place
)
555 -- Repurpose the tool's wear bar to display the set probability
556 probtool
:set_wear(math
.floor(((255-prob
)/255)*65535))
558 player
:set_wielded_item(probtool
)
561 if fields
.force_place
== "true" then
562 self
.force_place
= true
563 elseif fields
.force_place
== "false" then
564 self
.force_place
= false
573 --- Copies and modifies positions `pos1` and `pos2` so that each component of
574 -- `pos1` is less than or equal to the corresponding component of `pos2`.
575 -- Returns the new positions.
576 function schemedit
.sort_pos(pos1
, pos2
)
577 if not pos1
or not pos2
then
581 pos1
, pos2
= table.copy(pos1
), table.copy(pos2
)
582 if pos1
.x
> pos2
.x
then
583 pos2
.x
, pos1
.x
= pos1
.x
, pos2
.x
585 if pos1
.y
> pos2
.y
then
586 pos2
.y
, pos1
.y
= pos1
.y
, pos2
.y
588 if pos1
.z
> pos2
.z
then
589 pos2
.z
, pos1
.z
= pos1
.z
, pos2
.z
594 -- [function] Prepare size
595 function schemedit
.size(pos
)
596 local pos1
= vector
.new(pos
)
597 local meta
= minetest
.get_meta(pos
)
598 local node
= minetest
.get_node(pos
)
599 local param2
= node
.param2
601 x
= meta
:get_int("x_size"),
602 y
= math
.max(meta
:get_int("y_size") - 1, 0),
603 z
= meta
:get_int("z_size"),
607 local new_pos
= vector
.add({x
= size
.z
, y
= size
.y
, z
= -size
.x
}, pos
)
609 new_pos
.z
= new_pos
.z
+ 1
611 elseif param2
== 2 then
612 local new_pos
= vector
.add({x
= -size
.x
, y
= size
.y
, z
= -size
.z
}, pos
)
614 new_pos
.x
= new_pos
.x
+ 1
616 elseif param2
== 3 then
617 local new_pos
= vector
.add({x
= -size
.z
, y
= size
.y
, z
= size
.x
}, pos
)
619 new_pos
.z
= new_pos
.z
- 1
622 local new_pos
= vector
.add(size
, pos
)
624 new_pos
.x
= new_pos
.x
- 1
629 -- [function] Mark region
630 function schemedit
.mark(pos
)
631 schemedit
.unmark(pos
)
633 local id
= minetest
.hash_node_position(pos
)
634 local owner
= minetest
.get_meta(pos
):get_string("owner")
635 local pos1
, pos2
= schemedit
.size(pos
)
636 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
638 local thickness
= 0.2
639 local sizex
, sizey
, sizez
= (1 + pos2
.x
- pos1
.x
) / 2, (1 + pos2
.y
- pos1
.y
) / 2, (1 + pos2
.z
- pos1
.z
) / 2
645 for _
, z
in ipairs({pos1
.z
- 0.5, pos2
.z
+ 0.5}) do
651 local marker
= minetest
.add_entity({x
= pos1
.x
+ sizex
- 0.5, y
= pos1
.y
+ sizey
- 0.5, z
= z
+ offset
}, "schemedit:display")
652 if marker
~= nil then
653 marker
:set_properties({
654 visual_size
={x
=(sizex
+0.01) * 2, y
=sizey
* 2},
656 marker
:get_luaentity().id
= id
657 marker
:get_luaentity().owner
= owner
658 table.insert(m
, marker
)
665 for _
, x
in ipairs({pos1
.x
- 0.5, pos2
.x
+ 0.5}) do
672 local marker
= minetest
.add_entity({x
= x
+ offset
, y
= pos1
.y
+ sizey
- 0.5, z
= pos1
.z
+ sizez
- 0.5}, "schemedit:display")
673 if marker
~= nil then
674 marker
:set_properties({
675 visual_size
={x
=(sizez
+0.01) * 2, y
=sizey
* 2},
677 marker
:set_yaw(math
.pi
/ 2)
678 marker
:get_luaentity().id
= id
679 marker
:get_luaentity().owner
= owner
680 table.insert(m
, marker
)
685 schemedit
.markers
[id
] = m
689 -- [function] Unmark region
690 function schemedit
.unmark(pos
)
691 local id
= minetest
.hash_node_position(pos
)
692 if schemedit
.markers
[id
] then
694 for _
, entity
in ipairs(schemedit
.markers
[id
]) do
703 --- Mark node probability values near player
706 -- Show probability and force_place status of a particular position for player in HUD.
707 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
708 -- The distance to the node is also displayed below that. This can't be avoided and is
709 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
710 function schemedit
.display_node_prob(player
, pos
, prob
, force_place
)
712 if prob
and force_place
== true then
713 wpstring
= string.format("%d [F]", prob
)
716 elseif force_place
== true then
720 return player
:hud_add({
721 hud_elem_type
= "waypoint",
723 text
= "m", -- For the distance artifact
724 number = text_color_number
,
730 -- Display the node probabilities and force_place status of the nodes in a region.
731 -- By default, this is done for nodes near the player (distance: 5).
732 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
733 function schemedit
.display_node_probs_region(player
, pos1
, pos2
)
734 local playername
= player
:get_player_name()
735 local pos
= vector
.round(player
:get_pos())
738 -- Default: 5 nodes away from player in any direction
740 pos1
= vector
.subtract(pos
, dist
)
743 pos2
= vector
.add(pos
, dist
)
745 for x
=pos1
.x
, pos2
.x
do
746 for y
=pos1
.y
, pos2
.y
do
747 for z
=pos1
.z
, pos2
.z
do
748 local checkpos
= {x
=x
, y
=y
, z
=z
}
749 local nodehash
= minetest
.hash_node_position(checkpos
)
751 -- If node is already displayed, remove it so it can re replaced later
752 if displayed_waypoints
[playername
][nodehash
] then
753 player
:hud_remove(displayed_waypoints
[playername
][nodehash
])
754 displayed_waypoints
[playername
][nodehash
] = nil
757 local prob
, force_place
758 local meta
= minetest
.get_meta(checkpos
)
759 prob
= tonumber(meta
:get_string("schemedit_prob"))
760 force_place
= meta
:get_string("schemedit_force_place") == "true"
761 local hud_id
= schemedit
.display_node_prob(player
, checkpos
, prob
, force_place
)
763 displayed_waypoints
[playername
][nodehash
] = hud_id
764 displayed_waypoints
[playername
].display_active
= true
771 -- Remove all active displayed node statuses.
772 function schemedit
.clear_displayed_node_probs(player
)
773 local playername
= player
:get_player_name()
774 for nodehash
, hud_id
in pairs(displayed_waypoints
[playername
]) do
775 player
:hud_remove(hud_id
)
776 displayed_waypoints
[playername
][nodehash
] = nil
777 displayed_waypoints
[playername
].display_active
= false
781 minetest
.register_on_joinplayer(function(player
)
782 displayed_waypoints
[player
:get_player_name()] = {
783 display_active
= false -- If true, there *might* be at least one active node prob HUD display
784 -- If false, no node probabilities are displayed for sure.
788 minetest
.register_on_leaveplayer(function(player
)
789 displayed_waypoints
[player
:get_player_name()] = nil
792 -- Regularily clear the displayed node probabilities and force_place
793 -- for all players who do not wield the probtool.
794 -- This makes sure the screen is not spammed with information when it
797 minetest
.register_globalstep(function(dtime
)
798 cleartimer
= cleartimer
+ dtime
799 if cleartimer
> 2 then
800 local players
= minetest
.get_connected_players()
801 for p
= 1, #players
do
802 local player
= players
[p
]
803 local pname
= player
:get_player_name()
804 if displayed_waypoints
[pname
].display_active
then
805 local item
= player
:get_wielded_item()
806 if item
:get_name() ~= "schemedit:probtool" then
807 schemedit
.clear_displayed_node_probs(player
)
819 -- [priv] schematic_override
820 minetest
.register_privilege("schematic_override", {
821 description
= S("Allows you to access schemedit nodes not owned by you"),
822 give_to_singleplayer
= false,
825 -- [node] Schematic creator
826 minetest
.register_node("schemedit:creator", {
827 description
= S("Schematic Creator"),
828 _doc_items_longdesc
= S("The schematic creator is used to save a region of the world into a schematic file (.mts)."),
829 _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"..
830 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"..
831 S("The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.").."\n\n"..
832 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"..
833 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."),
834 tiles
= {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
835 "schemedit_creator_sides.png"},
836 groups
= { dig_immediate
= 2},
837 paramtype2
= "facedir",
838 is_ground_content
= false,
840 after_place_node
= function(pos
, player
)
841 local name
= player
:get_player_name()
842 local meta
= minetest
.get_meta(pos
)
844 meta
:set_string("owner", name
)
845 meta
:set_string("infotext", S("Schematic Creator").."\n"..S("(owned by @1)", name
))
846 meta
:set_string("prob_list", minetest
.serialize({}))
847 meta
:set_string("slices", minetest
.serialize({}))
849 local node
= minetest
.get_node(pos
)
850 local dir
= minetest
.facedir_to_dir(node
.param2
)
852 meta
:set_int("x_size", 1)
853 meta
:set_int("y_size", 1)
854 meta
:set_int("z_size", 1)
856 -- Don't take item from itemstack
859 can_dig
= function(pos
, player
)
860 local name
= player
:get_player_name()
861 local meta
= minetest
.get_meta(pos
)
862 if meta
:get_string("owner") == name
or
863 minetest
.check_player_privs(player
, "schematic_override") == true then
869 on_rightclick
= function(pos
, node
, player
)
870 local meta
= minetest
.get_meta(pos
)
871 local name
= player
:get_player_name()
872 if meta
:get_string("owner") == name
or
873 minetest
.check_player_privs(player
, "schematic_override") == true then
874 -- Get player attribute
875 local tab
= player
:get_attribute("schemedit:tab")
876 if not forms
[tab
] or not tab
then
880 schemedit
.show_formspec(pos
, player
, tab
, true)
883 after_destruct
= function(pos
)
884 schemedit
.unmark(pos
)
888 minetest
.register_tool("schemedit:probtool", {
889 description
= S("Schematic Node Probability Tool"),
890 _doc_items_longdesc
=
891 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"..
892 S("It allows you to set two things:").."\n"..
893 S("1) Set probability: Chance for any particular node to be actually placed (default: always placed)").."\n"..
894 S("2) Enable force placement: These nodes replace node other than air and ignore when placed in a schematic (default: off)"),
895 _doc_items_usagehelp
= "\n"..
896 S("BASIC USAGE:").."\n"..
897 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"..
898 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"..
899 S("NODE HUD:").."\n"..
900 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"..
901 S("To disable the node HUD, unselect the tool or hit “place” while not pointing anything.").."\n\n"..
902 S("UPDATING THE NODE HUD:").."\n"..
903 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."),
904 wield_image
= "schemedit_probtool.png",
905 inventory_image
= "schemedit_probtool.png",
906 liquids_pointable
= true,
907 groups
= { disable_repair
= 1 },
908 on_use
= function(itemstack
, user
, pointed_thing
)
909 local ctrl
= user
:get_player_control()
911 if not ctrl
.sneak
then
912 -- Open dialog to change the probability to apply to nodes
913 schemedit
.show_formspec(user
:get_pos(), user
, "probtool", true)
917 -- Display the probability and force_place values for nodes.
919 -- If a schematic creator was punched, only enable display for all nodes
920 -- within the creator's region.
921 local use_creator_region
= false
922 if pointed_thing
and pointed_thing
.type == "node" and pointed_thing
.under
then
923 punchpos
= pointed_thing
.under
924 local node
= minetest
.get_node(punchpos
)
925 if node
.name
== "schemedit:creator" then
926 local pos1
, pos2
= schemedit
.size(punchpos
)
927 pos1
, pos2
= schemedit
.sort_pos(pos1
, pos2
)
928 schemedit
.display_node_probs_region(user
, pos1
, pos2
)
933 -- Otherwise, just display the region close to the player
934 schemedit
.display_node_probs_region(user
)
937 on_secondary_use
= function(itemstack
, user
, pointed_thing
)
938 schemedit
.clear_displayed_node_probs(user
)
940 -- Set note probability and force_place and enable node probability display
941 on_place
= function(itemstack
, placer
, pointed_thing
)
942 -- Use pointed node's on_rightclick function first, if present
943 local node
= minetest
.get_node(pointed_thing
.under
)
944 if placer
and not placer
:get_player_control().sneak
then
945 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
946 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
950 -- This sets the node probability of pointed node to the
951 -- currently used probability stored in the tool.
952 local pos
= pointed_thing
.under
953 local node
= minetest
.get_node(pos
)
954 -- Schematic void are ignored, they always have probability 0
955 if node
.name
== "schemedit:void" then
958 local nmeta
= minetest
.get_meta(pos
)
959 local imeta
= itemstack
:get_meta()
960 local prob
= tonumber(imeta
:get_string("schemedit_prob"))
961 local force_place
= imeta
:get_string("schemedit_force_place")
963 if not prob
or prob
== 255 then
964 nmeta
:set_string("schemedit_prob", nil)
966 nmeta
:set_string("schemedit_prob", prob
)
968 if force_place
== "true" then
969 nmeta
:set_string("schemedit_force_place", "true")
971 nmeta
:set_string("schemedit_force_place", nil)
974 -- Enable node probablity display
975 schemedit
.display_node_probs_region(placer
)
981 minetest
.register_node("schemedit:void", {
982 description
= S("Schematic Void"),
983 _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."),
984 _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."),
985 tiles
= { "schemedit_void.png" },
986 drawtype
= "nodebox",
987 is_ground_content
= false,
990 sunlight_propagates
= true,
994 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
997 groups
= { dig_immediate
= 3},
1000 -- [entity] Visible schematic border
1001 minetest
.register_entity("schemedit:display", {
1002 visual
= "upright_sprite",
1003 textures
= {"schemedit_border.png"},
1004 visual_size
= {x
=10, y
=10},
1007 static_save
= false,
1008 glow
= minetest
.LIGHT_MAX
,
1010 on_step
= function(self
, dtime
)
1012 self
.object
:remove()
1013 elseif not schemedit
.markers
[self
.id
] then
1014 self
.object
:remove()
1017 on_activate
= function(self
)
1018 self
.object
:set_armor_groups({immortal
= 1})
1022 minetest
.register_lbm({
1023 label
= "Reset schematic creator border entities",
1024 name
= "schemedit:reset_border",
1025 nodenames
= "schemedit:creator",
1026 run_at_every_load
= true,
1027 action
= function(pos
, node
)
1028 local meta
= minetest
.get_meta(pos
)
1029 meta
:set_string("schem_border", "false")
1033 -- [chatcommand] Place schematic
1034 minetest
.register_chatcommand("placeschem", {
1035 description
= S("Place schematic at the position specified or the current player position (loaded from @1)", export_path_trunc
),
1036 privs
= {debug
= true},
1037 params
= S("<schematic name>[.mts] [<x> <y> <z>]"),
1038 func
= function(name
, param
)
1039 local schem
, p
= string.match(param
, "^([^ ]+) *(.*)$")
1040 local pos
= minetest
.string_to_pos(p
)
1043 return false, S("No schematic file specified.")
1047 pos
= minetest
.get_player_by_name(name
):get_pos()
1050 -- Automatiically add file name suffix if omitted
1052 if string.sub(schem
, string.len(schem
)-3, string.len(schem
)) == ".mts" then
1055 schem_full
= schem
.. ".mts"
1058 local success
= minetest
.place_schematic(pos
, export_path_full
.. DIR_DELIM
.. schem_full
, "random", nil, false)
1060 if success
== nil then
1061 return false, S("Schematic file could not be loaded!")