Don't expose full world path publicly
[minetest_schemedit.git] / init.lua
blob2364bad88e020b9542c36524d4d865c9d77d28f9
1 local S = minetest.get_translator("schemedit")
2 local F = minetest.formspec_escape
4 local schemedit = {}
6 -- Directory delimeter fallback (normally comes from builtin)
7 if not DIR_DELIM then
8 DIR_DELIM = "/"
9 end
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)
22 local res = {}
23 for _, i in pairs(t) do
24 res[#res + 1] = i
25 end
26 return res
27 end
29 ---
30 --- Formspec API
31 ---
33 local contexts = {}
34 local form_data = {}
35 local tabs = {}
36 local forms = {}
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)
46 end
48 function schemedit.schematic_prob_to_lua_prob(schematic_prob)
49 return schematic_prob * 2
51 end
53 -- [function] Add form
54 function schemedit.add_form(name, def)
55 def.name = name
56 forms[name] = def
58 if def.tab then
59 tabs[#tabs + 1] = name
60 end
61 end
63 -- [function] Generate tabs
64 function schemedit.generate_tabs(current)
65 local retval = "tabheader[0,0;tabs;"
66 for _, t in pairs(tabs) do
67 local f = forms[t]
68 if f.tab ~= false and f.caption then
69 retval = retval..f.caption..","
71 if type(current) ~= "number" and current == f.name then
72 current = _
73 end
74 end
75 end
76 retval = retval:sub(1, -2) -- Strip last comma
77 retval = retval..";"..current.."]" -- Close tabheader
78 return retval
79 end
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)
86 return true
87 end
88 end
90 -- [function] Show formspec
91 function schemedit.show_formspec(pos, player, tab, show, ...)
92 if forms[tab] then
93 if type(player) == "string" then
94 player = minetest.get_player_by_name(player)
95 end
96 local name = player:get_player_name()
98 if show ~= false then
99 if not form_data[name] then
100 form_data[name] = {}
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)
109 contexts[name] = pos
111 -- Update player attribute
112 if forms[tab].cache_name ~= false then
113 player:set_attribute("schemedit:tab", tab)
115 else
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
130 form_data[name] = {}
133 if not schemedit.handle_tabs(contexts[name], name, fields) and handle then
134 handle(form_data[name], contexts[name], name, fields)
138 end)
140 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
141 schemedit.scan_metadata = function(pos1, pos2)
142 local prob_list = {}
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
152 prob = 0
153 force_place = false
154 else
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")
159 if fp == "true" then
160 force_place = true
161 else
162 force_place = false
166 local hashpos = minetest.hash_node_position(scanpos)
167 prob_list[hashpos] = {
168 pos = scanpos,
169 prob = prob,
170 force_place = force_place,
176 return prob_list
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
191 prob_desc = ""
192 smeta:set_string("schemedit_prob", nil)
193 else
194 prob_desc = "\n"..S("Probability: @1", smeta:get_string("schemedit_prob") or
195 S("Not Set"))
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
209 desc = meta_desc
212 local original_desc = smeta:get_string("original_description")
213 if original_desc and original_desc ~= "" then
214 desc = original_desc
215 else
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)
228 return itemstack
232 --- Formspec Tabs
235 schemedit.add_form("main", {
236 tab = true,
237 caption = "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)
243 local border_button
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")).."]"
246 else
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
251 local form = [[
252 size[7,8]
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;X size:;]]..meta.x_size..[[]
265 field[2.8,7;2,1;y;Y size:;]]..meta.y_size..[[]
266 field[4.8,7;2,1;z;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;Save size]
272 ]]..
273 border_button
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")).."]"
278 return form
279 end,
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)
285 if fields.doc then
286 doc.show_entry(name, "nodes", "schemedit:creator", true)
287 return
290 -- Toggle border
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"
295 else
296 schemedit.mark(pos)
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)
308 if x then
309 meta.x_size = math.max(x, 1)
311 if y then
312 meta.y_size = math.max(y, 1)
314 if z then
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
325 -- Export schematic
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
330 minetest.mkdir(path)
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
337 prob = prob + 128
340 table.insert(probability_list, {
341 pos = minetest.get_position_from_hash(hash),
342 prob = prob,
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)
358 if res then
359 minetest.chat_send_player(name, minetest.colorize("#00ff00",
360 S("Exported schematic to @1", filepath)))
361 else
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})
371 -- Update border
372 if not fields.border and meta.schem_border == "true" then
373 schemedit.mark(pos)
376 -- Update formspec
377 if not fields.quit then
378 schemedit.show_formspec(pos, minetest.get_player_by_name(name), "main")
380 end,
383 schemedit.add_form("slice", {
384 caption = S("Y Slices"),
385 tab = true,
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)
392 local 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..F(insert)..","
397 slices = slices:sub(1, -2) -- Remove final comma
399 local form = [[
400 size[7,8]
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
415 form = form..[[
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]
420 ]]..done_button
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
429 form = form..[[
430 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
431 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
433 else
434 form = form..[[
435 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
436 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
441 return form
442 end,
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])
452 if fields.add then
453 if not self.panel_add then
454 self.panel_add = true
455 schemedit.show_formspec(pos, player, "slice")
456 else
457 self.panel_add = nil
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
477 self.panel_add = nil
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)))
486 -- Update formspec
487 self.selected = 1
488 self.panel_edit = nil
489 schemedit.show_formspec(pos, player, "slice")
492 if fields.edit then
493 if not self.panel_edit then
494 self.panel_edit = true
495 schemedit.show_formspec(pos, player, "slice")
496 else
497 self.panel_edit = nil
498 schemedit.show_formspec(pos, player, "slice")
501 end,
504 schemedit.add_form("probtool", {
505 cache_name = false,
506 caption = S("Schematic Node Probability Tool"),
507 get = function(self, pos, name)
508 local player = minetest.get_player_by_name(name)
509 if not player then
510 return
512 local probtool = player:get_wielded_item()
513 if probtool:get_name() ~= "schemedit:probtool" then
514 return
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")
521 if not prob then
522 prob = 255
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]"
536 return form
537 end,
538 handle = function(self, pos, name, fields)
539 if fields.submit then
540 local prob = tonumber(fields.prob)
541 if prob then
542 local player = minetest.get_player_by_name(name)
543 if not player then
544 return
546 local probtool = player:get_wielded_item()
547 if probtool:get_name() ~= "schemedit:probtool" then
548 return
551 local force_place = self.force_place == true
553 set_item_metadata(probtool, prob, force_place)
555 player:set_wielded_item(probtool)
558 if fields.force_place == "true" then
559 self.force_place = true
560 elseif fields.force_place == "false" then
561 self.force_place = false
563 end,
567 --- API
570 --- Copies and modifies positions `pos1` and `pos2` so that each component of
571 -- `pos1` is less than or equal to the corresponding component of `pos2`.
572 -- Returns the new positions.
573 function schemedit.sort_pos(pos1, pos2)
574 if not pos1 or not pos2 then
575 return
578 pos1, pos2 = table.copy(pos1), table.copy(pos2)
579 if pos1.x > pos2.x then
580 pos2.x, pos1.x = pos1.x, pos2.x
582 if pos1.y > pos2.y then
583 pos2.y, pos1.y = pos1.y, pos2.y
585 if pos1.z > pos2.z then
586 pos2.z, pos1.z = pos1.z, pos2.z
588 return pos1, pos2
591 -- [function] Prepare size
592 function schemedit.size(pos)
593 local pos1 = vector.new(pos)
594 local meta = minetest.get_meta(pos)
595 local node = minetest.get_node(pos)
596 local param2 = node.param2
597 local size = {
598 x = meta:get_int("x_size"),
599 y = math.max(meta:get_int("y_size") - 1, 0),
600 z = meta:get_int("z_size"),
603 if param2 == 1 then
604 local new_pos = vector.add({x = size.z, y = size.y, z = -size.x}, pos)
605 pos1.x = pos1.x + 1
606 new_pos.z = new_pos.z + 1
607 return pos1, new_pos
608 elseif param2 == 2 then
609 local new_pos = vector.add({x = -size.x, y = size.y, z = -size.z}, pos)
610 pos1.z = pos1.z - 1
611 new_pos.x = new_pos.x + 1
612 return pos1, new_pos
613 elseif param2 == 3 then
614 local new_pos = vector.add({x = -size.z, y = size.y, z = size.x}, pos)
615 pos1.x = pos1.x - 1
616 new_pos.z = new_pos.z - 1
617 return pos1, new_pos
618 else
619 local new_pos = vector.add(size, pos)
620 pos1.z = pos1.z + 1
621 new_pos.x = new_pos.x - 1
622 return pos1, new_pos
626 -- [function] Mark region
627 function schemedit.mark(pos)
628 schemedit.unmark(pos)
630 local id = minetest.hash_node_position(pos)
631 local owner = minetest.get_meta(pos):get_string("owner")
632 local pos1, pos2 = schemedit.size(pos)
633 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
635 local thickness = 0.2
636 local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
637 local m = {}
638 local low = true
639 local offset
641 -- XY plane markers
642 for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
643 if low then
644 offset = -0.01
645 else
646 offset = 0.01
648 local marker = minetest.add_entity({x = pos1.x + sizex - 0.5, y = pos1.y + sizey - 0.5, z = z + offset}, "schemedit:display")
649 if marker ~= nil then
650 marker:set_properties({
651 visual_size={x=(sizex+0.01) * 2, y=sizey * 2},
653 marker:get_luaentity().id = id
654 marker:get_luaentity().owner = owner
655 table.insert(m, marker)
657 low = false
660 low = true
661 -- YZ plane markers
662 for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
663 if low then
664 offset = -0.01
665 else
666 offset = 0.01
669 local marker = minetest.add_entity({x = x + offset, y = pos1.y + sizey - 0.5, z = pos1.z + sizez - 0.5}, "schemedit:display")
670 if marker ~= nil then
671 marker:set_properties({
672 visual_size={x=(sizez+0.01) * 2, y=sizey * 2},
674 marker:set_yaw(math.pi / 2)
675 marker:get_luaentity().id = id
676 marker:get_luaentity().owner = owner
677 table.insert(m, marker)
679 low = false
682 schemedit.markers[id] = m
683 return true
686 -- [function] Unmark region
687 function schemedit.unmark(pos)
688 local id = minetest.hash_node_position(pos)
689 if schemedit.markers[id] then
690 local retval
691 for _, entity in ipairs(schemedit.markers[id]) do
692 entity:remove()
693 retval = true
695 return retval
700 --- Mark node probability values near player
703 -- Show probability and force_place status of a particular position for player in HUD.
704 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
705 -- The distance to the node is also displayed below that. This can't be avoided and is
706 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
707 function schemedit.display_node_prob(player, pos, prob, force_place)
708 local wpstring
709 if prob and force_place == true then
710 wpstring = string.format("%d [F]", prob)
711 elseif prob then
712 wpstring = prob
713 elseif force_place == true then
714 wpstring = "[F]"
716 if wpstring then
717 return player:hud_add({
718 hud_elem_type = "waypoint",
719 name = wpstring,
720 text = "m", -- For the distance artifact
721 number = text_color_number,
722 world_pos = pos,
727 -- Display the node probabilities and force_place status of the nodes in a region.
728 -- By default, this is done for nodes near the player (distance: 5).
729 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
730 function schemedit.display_node_probs_region(player, pos1, pos2)
731 local playername = player:get_player_name()
732 local pos = vector.round(player:get_pos())
734 local dist = 5
735 -- Default: 5 nodes away from player in any direction
736 if not pos1 then
737 pos1 = vector.subtract(pos, dist)
739 if not pos2 then
740 pos2 = vector.add(pos, dist)
742 for x=pos1.x, pos2.x do
743 for y=pos1.y, pos2.y do
744 for z=pos1.z, pos2.z do
745 local checkpos = {x=x, y=y, z=z}
746 local nodehash = minetest.hash_node_position(checkpos)
748 -- If node is already displayed, remove it so it can re replaced later
749 if displayed_waypoints[playername][nodehash] then
750 player:hud_remove(displayed_waypoints[playername][nodehash])
751 displayed_waypoints[playername][nodehash] = nil
754 local prob, force_place
755 local meta = minetest.get_meta(checkpos)
756 prob = tonumber(meta:get_string("schemedit_prob"))
757 force_place = meta:get_string("schemedit_force_place") == "true"
758 local hud_id = schemedit.display_node_prob(player, checkpos, prob, force_place)
759 if hud_id then
760 displayed_waypoints[playername][nodehash] = hud_id
761 displayed_waypoints[playername].display_active = true
768 -- Remove all active displayed node statuses.
769 function schemedit.clear_displayed_node_probs(player)
770 local playername = player:get_player_name()
771 for nodehash, hud_id in pairs(displayed_waypoints[playername]) do
772 player:hud_remove(hud_id)
773 displayed_waypoints[playername][nodehash] = nil
774 displayed_waypoints[playername].display_active = false
778 minetest.register_on_joinplayer(function(player)
779 displayed_waypoints[player:get_player_name()] = {
780 display_active = false -- If true, there *might* be at least one active node prob HUD display
781 -- If false, no node probabilities are displayed for sure.
783 end)
785 minetest.register_on_leaveplayer(function(player)
786 displayed_waypoints[player:get_player_name()] = nil
787 end)
789 -- Regularily clear the displayed node probabilities and force_place
790 -- for all players who do not wield the probtool.
791 -- This makes sure the screen is not spammed with information when it
792 -- isn't needed.
793 local cleartimer = 0
794 minetest.register_globalstep(function(dtime)
795 cleartimer = cleartimer + dtime
796 if cleartimer > 2 then
797 local players = minetest.get_connected_players()
798 for p = 1, #players do
799 local player = players[p]
800 local pname = player:get_player_name()
801 if displayed_waypoints[pname].display_active then
802 local item = player:get_wielded_item()
803 if item:get_name() ~= "schemedit:probtool" then
804 schemedit.clear_displayed_node_probs(player)
808 cleartimer = 0
810 end)
813 --- Registrations
816 -- [priv] schematic_override
817 minetest.register_privilege("schematic_override", {
818 description = S("Allows you to access schemedit nodes not owned by you"),
819 give_to_singleplayer = false,
822 -- [node] Schematic creator
823 minetest.register_node("schemedit:creator", {
824 description = S("Schematic Creator"),
825 _doc_items_longdesc = S("The schematic creator is used to save a region of the world into a schematic file (.mts)."),
826 _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"..
827 S("To save a region, rightclick the block, enter the size, 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"..
828 S("The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.").."\n\n"..
829 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 occours 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 occour always.").."\n\n"..
830 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."),
831 tiles = {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
832 "schemedit_creator_sides.png"},
833 groups = { dig_immediate = 2},
834 paramtype2 = "facedir",
835 is_ground_content = false,
837 after_place_node = function(pos, player)
838 local name = player:get_player_name()
839 local meta = minetest.get_meta(pos)
841 meta:set_string("owner", name)
842 meta:set_string("infotext", S("Schematic Creator").."\n"..S("(owned by @1)", name))
843 meta:set_string("prob_list", minetest.serialize({}))
844 meta:set_string("slices", minetest.serialize({}))
846 local node = minetest.get_node(pos)
847 local dir = minetest.facedir_to_dir(node.param2)
849 meta:set_int("x_size", 1)
850 meta:set_int("y_size", 1)
851 meta:set_int("z_size", 1)
853 -- Don't take item from itemstack
854 return true
855 end,
856 can_dig = function(pos, player)
857 local name = player:get_player_name()
858 local meta = minetest.get_meta(pos)
859 if meta:get_string("owner") == name or
860 minetest.check_player_privs(player, "schematic_override") == true then
861 return true
864 return false
865 end,
866 on_rightclick = function(pos, node, player)
867 local meta = minetest.get_meta(pos)
868 local name = player:get_player_name()
869 if meta:get_string("owner") == name or
870 minetest.check_player_privs(player, "schematic_override") == true then
871 -- Get player attribute
872 local tab = player:get_attribute("schemedit:tab")
873 if not forms[tab] or not tab then
874 tab = "main"
877 schemedit.show_formspec(pos, player, tab, true)
879 end,
880 after_destruct = function(pos)
881 schemedit.unmark(pos)
882 end,
885 minetest.register_tool("schemedit:probtool", {
886 description = S("Schematic Node Probability Tool"),
887 _doc_items_longdesc =
888 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"..
889 S("It allows you to set two things:").."\n"..
890 S("1) Set probability: Chance for any particular node to be actually placed (default: always placed)").."\n"..
891 S("2) Enable force placement: These nodes replace node other than air and ignored when placed in a schematic (default: off)"),
892 _doc_items_usagehelp = "\n"..
893 S("BASIC USAGE:").."\n"..
894 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"..
895 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"..
896 S("NODE HUD:").."\n"..
897 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"..
898 S("To disable the node HUD, unselect the tool or hit “place” while not pointing anything.").."\n\n"..
899 S("UPDATING THE NODE HUD:").."\n"..
900 S("The node HUD is not updated automatically any 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."),
901 wield_image = "schemedit_probtool.png",
902 inventory_image = "schemedit_probtool.png",
903 liquids_pointable = true,
904 on_use = function(itemstack, user, pointed_thing)
905 local ctrl = user:get_player_control()
906 -- Simple use
907 if not ctrl.sneak then
908 -- Open dialog to change the probability to apply to nodes
909 schemedit.show_formspec(user:get_pos(), user, "probtool", true)
911 -- Use + sneak
912 else
913 -- Display the probability and force_place values for nodes.
915 -- If a schematic creator was punched, only enable display for all nodes
916 -- within the creator's region.
917 local use_creator_region = false
918 if pointed_thing and pointed_thing.type == "node" and pointed_thing.under then
919 punchpos = pointed_thing.under
920 local node = minetest.get_node(punchpos)
921 if node.name == "schemedit:creator" then
922 local pos1, pos2 = schemedit.size(punchpos)
923 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
924 schemedit.display_node_probs_region(user, pos1, pos2)
925 return
929 -- Otherwise, just display the region close to the player
930 schemedit.display_node_probs_region(user)
932 end,
933 on_secondary_use = function(itemstack, user, pointed_thing)
934 schemedit.clear_displayed_node_probs(user)
935 end,
936 -- Set note probability and force_place and enable node probability display
937 on_place = function(itemstack, placer, pointed_thing)
938 -- Use pointed node's on_rightclick function first, if present
939 local node = minetest.get_node(pointed_thing.under)
940 if placer and not placer:get_player_control().sneak then
941 if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
942 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
946 -- This sets the node probability of pointed node to the
947 -- currently used probability stored in the tool.
948 local pos = pointed_thing.under
949 local node = minetest.get_node(pos)
950 -- Schematic void are ignored, they always have probability 0
951 if node.name == "schemedit:void" then
952 return itemstack
954 local nmeta = minetest.get_meta(pos)
955 local imeta = itemstack:get_meta()
956 local prob = tonumber(imeta:get_string("schemedit_prob"))
957 local force_place = imeta:get_string("schemedit_force_place")
959 if not prob or prob == 255 then
960 nmeta:set_string("schemedit_prob", nil)
961 else
962 nmeta:set_string("schemedit_prob", prob)
964 if force_place == "true" then
965 nmeta:set_string("schemedit_force_place", "true")
966 else
967 nmeta:set_string("schemedit_force_place", nil)
970 -- Enable node probablity display
971 schemedit.display_node_probs_region(placer)
973 return itemstack
974 end,
977 minetest.register_node("schemedit:void", {
978 description = S("Schematic Void"),
979 _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."),
980 _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."),
981 tiles = { "schemedit_void.png" },
982 drawtype = "nodebox",
983 is_ground_content = false,
984 paramtype = "light",
985 walkable = false,
986 sunlight_propagates = true,
987 node_box = {
988 type = "fixed",
989 fixed = {
990 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
993 groups = { dig_immediate = 3},
996 -- [entity] Visible schematic border
997 minetest.register_entity("schemedit:display", {
998 visual = "upright_sprite",
999 textures = {"schemedit_border.png"},
1000 visual_size = {x=10, y=10},
1001 collisionbox = {0,0,0,0,0,0},
1002 physical = false,
1004 on_step = function(self, dtime)
1005 if not self.id then
1006 self.object:remove()
1007 elseif not schemedit.markers[self.id] then
1008 self.object:remove()
1010 end,
1011 on_activate = function(self)
1012 self.object:set_armor_groups({immortal = 1})
1013 end,
1016 -- [chatcommand] Place schematic
1017 minetest.register_chatcommand("placeschem", {
1018 description = S("Place schematic at the position specified or the current player position (loaded from @1)", export_path_trunc),
1019 privs = {debug = true},
1020 params = S("<schematic name>[.mts] [<x> <y> <z>]"),
1021 func = function(name, param)
1022 local schem, p = string.match(param, "^([^ ]+) *(.*)$")
1023 local pos = minetest.string_to_pos(p)
1025 if not schem then
1026 return false, S("No schematic file specified.")
1029 if not pos then
1030 pos = minetest.get_player_by_name(name):get_pos()
1033 -- Automatiically add file name suffix if omitted
1034 local schem_full
1035 if string.sub(schem, string.len(schem)-3, string.len(schem)) == ".mts" then
1036 schem_full = schem
1037 else
1038 schem_full = schem .. ".mts"
1041 local success = minetest.place_schematic(pos, export_path_full .. DIR_DELIM .. schem_full, "random", nil, false)
1043 if success == nil then
1044 return false, S("Schematic file could not be loaded!")
1045 else
1046 return true
1048 end,