Make mod translatable
[minetest_schemedit.git] / init.lua
blob34ecdaab30e09f898c6b01c49d154e58d3c456dd
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 local text_color = "#D79E9E"
13 local text_color_number = 0xD79E9E
15 schemedit.markers = {}
17 -- [local function] Renumber table
18 local function renumber(t)
19 local res = {}
20 for _, i in pairs(t) do
21 res[#res + 1] = i
22 end
23 return res
24 end
26 ---
27 --- Formspec API
28 ---
30 local contexts = {}
31 local form_data = {}
32 local tabs = {}
33 local forms = {}
34 local displayed_waypoints = {}
36 -- Sadly, the probabilities presented in Lua (0-255) are not identical to the REAL probabilities in the
37 -- schematic file (0-127). There are two converter functions to convert from one probability type to another.
38 -- This mod tries to retain the “Lua probability” as long as possible and only switches to “schematic probability”
39 -- on an actual export to a schematic.
41 function schemedit.lua_prob_to_schematic_prob(lua_prob)
42 return math.floor(lua_prob / 2)
43 end
45 function schemedit.schematic_prob_to_lua_prob(schematic_prob)
46 return schematic_prob * 2
48 end
50 -- [function] Add form
51 function schemedit.add_form(name, def)
52 def.name = name
53 forms[name] = def
55 if def.tab then
56 tabs[#tabs + 1] = name
57 end
58 end
60 -- [function] Generate tabs
61 function schemedit.generate_tabs(current)
62 local retval = "tabheader[0,0;tabs;"
63 for _, t in pairs(tabs) do
64 local f = forms[t]
65 if f.tab ~= false and f.caption then
66 retval = retval..f.caption..","
68 if type(current) ~= "number" and current == f.name then
69 current = _
70 end
71 end
72 end
73 retval = retval:sub(1, -2) -- Strip last comma
74 retval = retval..";"..current.."]" -- Close tabheader
75 return retval
76 end
78 -- [function] Handle tabs
79 function schemedit.handle_tabs(pos, name, fields)
80 local tab = tonumber(fields.tabs)
81 if tab and tabs[tab] and forms[tabs[tab]] then
82 schemedit.show_formspec(pos, name, forms[tabs[tab]].name)
83 return true
84 end
85 end
87 -- [function] Show formspec
88 function schemedit.show_formspec(pos, player, tab, show, ...)
89 if forms[tab] then
90 if type(player) == "string" then
91 player = minetest.get_player_by_name(player)
92 end
93 local name = player:get_player_name()
95 if show ~= false then
96 if not form_data[name] then
97 form_data[name] = {}
98 end
100 local form = forms[tab].get(form_data[name], pos, name, ...)
101 if forms[tab].tab then
102 form = form..schemedit.generate_tabs(tab)
105 minetest.show_formspec(name, "schemedit:"..tab, form)
106 contexts[name] = pos
108 -- Update player attribute
109 if forms[tab].cache_name ~= false then
110 player:set_attribute("schemedit:tab", tab)
112 else
113 minetest.close_formspec(pname, "schemedit:"..tab)
118 -- [event] On receive fields
119 minetest.register_on_player_receive_fields(function(player, formname, fields)
120 local formname = formname:split(":")
122 if formname[1] == "schemedit" and forms[formname[2]] then
123 local handle = forms[formname[2]].handle
124 local name = player:get_player_name()
125 if contexts[name] then
126 if not form_data[name] then
127 form_data[name] = {}
130 if not schemedit.handle_tabs(contexts[name], name, fields) and handle then
131 handle(form_data[name], contexts[name], name, fields)
135 end)
137 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
138 schemedit.scan_metadata = function(pos1, pos2)
139 local prob_list = {}
141 for x=pos1.x, pos2.x do
142 for y=pos1.y, pos2.y do
143 for z=pos1.z, pos2.z do
144 local scanpos = {x=x, y=y, z=z}
145 local node = minetest.get_node_or_nil(scanpos)
147 local prob, force_place
148 if node == nil or node.name == "schemedit:void" then
149 prob = 0
150 force_place = false
151 else
152 local meta = minetest.get_meta(scanpos)
154 prob = tonumber(meta:get_string("schemedit_prob")) or 255
155 local fp = meta:get_string("schemedit_force_place")
156 if fp == "true" then
157 force_place = true
158 else
159 force_place = false
163 local hashpos = minetest.hash_node_position(scanpos)
164 prob_list[hashpos] = {
165 pos = scanpos,
166 prob = prob,
167 force_place = force_place,
173 return prob_list
176 -- Sets probability and force_place metadata of an item.
177 -- Also updates item description.
178 -- The itemstack is updated in-place.
179 local function set_item_metadata(itemstack, prob, force_place)
180 local smeta = itemstack:get_meta()
181 local prob_desc = "\n"..S("Probability: @1", prob or
182 smeta:get_string("schemedit_prob") or S("Not Set"))
183 -- Update probability
184 if prob and prob >= 0 and prob < 255 then
185 smeta:set_string("schemedit_prob", tostring(prob))
186 elseif prob and prob == 255 then
187 -- Clear prob metadata for default probability
188 prob_desc = ""
189 smeta:set_string("schemedit_prob", nil)
190 else
191 prob_desc = "\n"..S("Probability: @1", smeta:get_string("schemedit_prob") or
192 S("Not Set"))
195 -- Update force place
196 if force_place == true then
197 smeta:set_string("schemedit_force_place", "true")
198 elseif force_place == false then
199 smeta:set_string("schemedit_force_place", nil)
202 -- Update description
203 local desc = minetest.registered_items[itemstack:get_name()].description
204 local meta_desc = smeta:get_string("description")
205 if meta_desc and meta_desc ~= "" then
206 desc = meta_desc
209 local original_desc = smeta:get_string("original_description")
210 if original_desc and original_desc ~= "" then
211 desc = original_desc
212 else
213 smeta:set_string("original_description", desc)
216 local force_desc = ""
217 if smeta:get_string("schemedit_force_place") == "true" then
218 force_desc = "\n"..S("Force placement")
221 desc = desc..minetest.colorize(text_color, prob_desc..force_desc)
223 smeta:set_string("description", desc)
225 return itemstack
229 --- Formspec Tabs
232 schemedit.add_form("main", {
233 tab = true,
234 caption = "Main",
235 get = function(self, pos, name)
236 local meta = minetest.get_meta(pos):to_table().fields
237 local strpos = minetest.pos_to_string(pos)
238 local hashpos = minetest.hash_node_position(pos)
240 local border_button
241 if meta.schem_border == "true" and schemedit.markers[hashpos] then
242 border_button = "button[3.5,7.5;3,1;border;"..F(S("Hide border")).."]"
243 else
244 border_button = "button[3.5,7.5;3,1;border;"..F(S("Show border")).."]"
247 -- TODO: Show information regarding volume, pos1, pos2, etc... in formspec
248 local form = [[
249 size[7,8]
250 label[0.5,-0.1;]]..F(S("Position: @1", strpos))..[[]
251 label[3,-0.1;]]..F(S("Owner: @1", name))..[[]
253 field[0.8,1;5,1;name;]]..F(S("Schematic name:"))..[[;]]..F(meta.schem_name or "")..[[]
254 button[5.3,0.69;1.2,1;save_name;]]..F(S("Save"))..[[]
255 tooltip[save_name;]]..F(S("Save schematic name"))..[[]
256 field_close_on_enter[name;false]
258 button[0.5,1.5;6,1;export;]]..F(S("Export schematic"))..[[]
259 textarea[0.8,2.5;6.2,5;;]]..F(S("The schematic will be exported as a .mts file and stored in\n@1",
260 export_path_full .. DIR_DELIM .. "<name>.mts."))..[[;]
261 field[0.8,7;2,1;x;X size:;]]..meta.x_size..[[]
262 field[2.8,7;2,1;y;Y size:;]]..meta.y_size..[[]
263 field[4.8,7;2,1;z;Z size:;]]..meta.z_size..[[]
264 field_close_on_enter[x;false]
265 field_close_on_enter[y;false]
266 field_close_on_enter[z;false]
268 button[0.5,7.5;3,1;save;Save size]
269 ]]..
270 border_button
271 if minetest.get_modpath("doc") then
272 form = form .. "image_button[6.4,-0.2;0.8,0.8;doc_button_icon_lores.png;doc;]" ..
273 "tooltip[doc;"..F(S("Help")).."]"
275 return form
276 end,
277 handle = function(self, pos, name, fields)
278 local realmeta = minetest.get_meta(pos)
279 local meta = realmeta:to_table().fields
280 local hashpos = minetest.hash_node_position(pos)
282 if fields.doc then
283 doc.show_entry(name, "nodes", "schemedit:creator", true)
284 return
287 -- Toggle border
288 if fields.border then
289 if meta.schem_border == "true" and schemedit.markers[hashpos] then
290 schemedit.unmark(pos)
291 meta.schem_border = "false"
292 else
293 schemedit.mark(pos)
294 meta.schem_border = "true"
298 -- Save size vector values
299 if (fields.save or fields.key_enter_field == "x" or
300 fields.key_enter_field == "y" or fields.key_enter_field == "z")
301 and (fields.x and fields.y and fields.z and fields.x ~= ""
302 and fields.y ~= "" and fields.z ~= "") then
303 local x, y, z = tonumber(fields.x), tonumber(fields.y), tonumber(fields.z)
305 if x then
306 meta.x_size = math.max(x, 1)
308 if y then
309 meta.y_size = math.max(y, 1)
311 if z then
312 meta.z_size = math.max(z, 1)
316 -- Save schematic name
317 if fields.save_name or fields.key_enter_field == "name" and fields.name and
318 fields.name ~= "" then
319 meta.schem_name = fields.name
322 -- Export schematic
323 if fields.export and meta.schem_name and meta.schem_name ~= "" then
324 local pos1, pos2 = schemedit.size(pos)
325 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
326 local path = export_path_full .. DIR_DELIM
327 minetest.mkdir(path)
329 local plist = schemedit.scan_metadata(pos1, pos2)
330 local probability_list = {}
331 for hash, i in pairs(plist) do
332 local prob = schemedit.lua_prob_to_schematic_prob(i.prob)
333 if i.force_place == true then
334 prob = prob + 128
337 table.insert(probability_list, {
338 pos = minetest.get_position_from_hash(hash),
339 prob = prob,
343 local slist = minetest.deserialize(meta.slices)
344 local slice_list = {}
345 for _, i in pairs(slist) do
346 slice_list[#slice_list + 1] = {
347 ypos = pos.y + i.ypos,
348 prob = schemedit.lua_prob_to_schematic_prob(i.prob),
352 local filepath = path..meta.schem_name..".mts"
353 local res = minetest.create_schematic(pos1, pos2, probability_list, filepath, slice_list)
355 if res then
356 minetest.chat_send_player(name, minetest.colorize("#00ff00",
357 S("Exported schematic to @1", filepath)))
358 else
359 minetest.chat_send_player(name, minetest.colorize("red",
360 S("Failed to export schematic to @1", filepath)))
364 -- Save meta before updating visuals
365 local inv = realmeta:get_inventory():get_lists()
366 realmeta:from_table({fields = meta, inventory = inv})
368 -- Update border
369 if not fields.border and meta.schem_border == "true" then
370 schemedit.mark(pos)
373 -- Update formspec
374 if not fields.quit then
375 schemedit.show_formspec(pos, minetest.get_player_by_name(name), "main")
377 end,
380 schemedit.add_form("slice", {
381 caption = S("Y Slices"),
382 tab = true,
383 get = function(self, pos, name, visible_panel)
384 local meta = minetest.get_meta(pos):to_table().fields
386 self.selected = self.selected or 1
387 local selected = tostring(self.selected)
388 local slice_list = minetest.deserialize(meta.slices)
389 local slices = ""
390 for _, i in pairs(slice_list) do
391 local insert = F(S("Y = @1; Probability = @2", tostring(i.ypos), tostring(i.prob)))
392 slices = slices..F(insert)..","
394 slices = slices:sub(1, -2) -- Remove final comma
396 local form = [[
397 size[7,8]
398 table[0,0;6.8,6;slices;]]..slices..[[;]]..selected..[[]
401 if self.panel_add or self.panel_edit then
402 local ypos_default, prob_default = "", ""
403 local done_button = "button[5,7.18;2,1;done_add;"..F(S("Done")).."]"
404 if self.panel_edit then
405 done_button = "button[5,7.18;2,1;done_edit;"..F(S("Done")).."]"
406 if slice_list[self.selected] then
407 ypos_default = slice_list[self.selected].ypos
408 prob_default = slice_list[self.selected].prob
412 form = form..[[
413 field[0.3,7.5;2.5,1;ypos;]]..F(S("Y position (max. @1):", (meta.y_size - 1)))..[[;]]..ypos_default..[[]
414 field[2.8,7.5;2.5,1;prob;]]..F(S("Probability (0-255):"))..[[;]]..prob_default..[[]
415 field_close_on_enter[ypos;false]
416 field_close_on_enter[prob;false]
417 ]]..done_button
420 if not self.panel_edit then
421 form = form.."button[0,6;2,1;add;"..F(S("+ Add slice")).."]"
424 if slices ~= "" and self.selected and not self.panel_add then
425 if not self.panel_edit then
426 form = form..[[
427 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
428 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
430 else
431 form = form..[[
432 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
433 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
438 return form
439 end,
440 handle = function(self, pos, name, fields)
441 local meta = minetest.get_meta(pos)
442 local player = minetest.get_player_by_name(name)
444 if fields.slices then
445 local slices = fields.slices:split(":")
446 self.selected = tonumber(slices[2])
449 if fields.add then
450 if not self.panel_add then
451 self.panel_add = true
452 schemedit.show_formspec(pos, player, "slice")
453 else
454 self.panel_add = nil
455 schemedit.show_formspec(pos, player, "slice")
459 local ypos, prob = tonumber(fields.ypos), tonumber(fields.prob)
460 if (fields.done_add or fields.done_edit) and fields.ypos and fields.prob and
461 fields.ypos ~= "" and fields.prob ~= "" and ypos and prob and
462 ypos <= (meta:get_int("y_size") - 1) and prob >= 0 and prob <= 255 then
463 local slice_list = minetest.deserialize(meta:get_string("slices"))
464 local index = #slice_list + 1
465 if fields.done_edit then
466 index = self.selected
469 slice_list[index] = {ypos = ypos, prob = prob}
471 meta:set_string("slices", minetest.serialize(slice_list))
473 -- Update and show formspec
474 self.panel_add = nil
475 schemedit.show_formspec(pos, player, "slice")
478 if fields.remove and self.selected then
479 local slice_list = minetest.deserialize(meta:get_string("slices"))
480 slice_list[self.selected] = nil
481 meta:set_string("slices", minetest.serialize(renumber(slice_list)))
483 -- Update formspec
484 self.selected = 1
485 self.panel_edit = nil
486 schemedit.show_formspec(pos, player, "slice")
489 if fields.edit then
490 if not self.panel_edit then
491 self.panel_edit = true
492 schemedit.show_formspec(pos, player, "slice")
493 else
494 self.panel_edit = nil
495 schemedit.show_formspec(pos, player, "slice")
498 end,
501 schemedit.add_form("probtool", {
502 cache_name = false,
503 caption = S("Schematic Node Probability Tool"),
504 get = function(self, pos, name)
505 local player = minetest.get_player_by_name(name)
506 if not player then
507 return
509 local probtool = player:get_wielded_item()
510 if probtool:get_name() ~= "schemedit:probtool" then
511 return
514 local meta = probtool:get_meta()
515 local prob = tonumber(meta:get_string("schemedit_prob"))
516 local force_place = meta:get_string("schemedit_force_place")
518 if not prob then
519 prob = 255
521 if force_place == nil or force_place == "" then
522 force_place = "false"
524 local form = "size[5,4]"..
525 "label[0,0;"..F(S("Schematic Node Probability Tool")).."]"..
526 "field[0.75,1;4,1;prob;"..F(S("Probability (0-255)"))..";"..prob.."]"..
527 "checkbox[0.60,1.5;force_place;"..F(S("Force placement"))..";" .. force_place .. "]" ..
528 "button_exit[0.25,3;2,1;cancel;"..F(S("Cancel")).."]"..
529 "button_exit[2.75,3;2,1;submit;"..F(S("Apply")).."]"..
530 "tooltip[prob;"..F(S("Probability that the node will be placed")).."]"..
531 "tooltip[force_place;"..F(S("If enabled, the node will replace nodes other than air and ignore")).."]"..
532 "field_close_on_enter[prob;false]"
533 return form
534 end,
535 handle = function(self, pos, name, fields)
536 if fields.submit then
537 local prob = tonumber(fields.prob)
538 if prob then
539 local player = minetest.get_player_by_name(name)
540 if not player then
541 return
543 local probtool = player:get_wielded_item()
544 if probtool:get_name() ~= "schemedit:probtool" then
545 return
548 local force_place = self.force_place == true
550 set_item_metadata(probtool, prob, force_place)
552 player:set_wielded_item(probtool)
555 if fields.force_place == "true" then
556 self.force_place = true
557 elseif fields.force_place == "false" then
558 self.force_place = false
560 end,
564 --- API
567 --- Copies and modifies positions `pos1` and `pos2` so that each component of
568 -- `pos1` is less than or equal to the corresponding component of `pos2`.
569 -- Returns the new positions.
570 function schemedit.sort_pos(pos1, pos2)
571 if not pos1 or not pos2 then
572 return
575 pos1, pos2 = table.copy(pos1), table.copy(pos2)
576 if pos1.x > pos2.x then
577 pos2.x, pos1.x = pos1.x, pos2.x
579 if pos1.y > pos2.y then
580 pos2.y, pos1.y = pos1.y, pos2.y
582 if pos1.z > pos2.z then
583 pos2.z, pos1.z = pos1.z, pos2.z
585 return pos1, pos2
588 -- [function] Prepare size
589 function schemedit.size(pos)
590 local pos1 = vector.new(pos)
591 local meta = minetest.get_meta(pos)
592 local node = minetest.get_node(pos)
593 local param2 = node.param2
594 local size = {
595 x = meta:get_int("x_size"),
596 y = math.max(meta:get_int("y_size") - 1, 0),
597 z = meta:get_int("z_size"),
600 if param2 == 1 then
601 local new_pos = vector.add({x = size.z, y = size.y, z = -size.x}, pos)
602 pos1.x = pos1.x + 1
603 new_pos.z = new_pos.z + 1
604 return pos1, new_pos
605 elseif param2 == 2 then
606 local new_pos = vector.add({x = -size.x, y = size.y, z = -size.z}, pos)
607 pos1.z = pos1.z - 1
608 new_pos.x = new_pos.x + 1
609 return pos1, new_pos
610 elseif param2 == 3 then
611 local new_pos = vector.add({x = -size.z, y = size.y, z = size.x}, pos)
612 pos1.x = pos1.x - 1
613 new_pos.z = new_pos.z - 1
614 return pos1, new_pos
615 else
616 local new_pos = vector.add(size, pos)
617 pos1.z = pos1.z + 1
618 new_pos.x = new_pos.x - 1
619 return pos1, new_pos
623 -- [function] Mark region
624 function schemedit.mark(pos)
625 schemedit.unmark(pos)
627 local id = minetest.hash_node_position(pos)
628 local owner = minetest.get_meta(pos):get_string("owner")
629 local pos1, pos2 = schemedit.size(pos)
630 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
632 local thickness = 0.2
633 local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
634 local m = {}
635 local low = true
636 local offset
638 -- XY plane markers
639 for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
640 if low then
641 offset = -0.01
642 else
643 offset = 0.01
645 local marker = minetest.add_entity({x = pos1.x + sizex - 0.5, y = pos1.y + sizey - 0.5, z = z + offset}, "schemedit:display")
646 if marker ~= nil then
647 marker:set_properties({
648 visual_size={x=(sizex+0.01) * 2, y=sizey * 2},
650 marker:get_luaentity().id = id
651 marker:get_luaentity().owner = owner
652 table.insert(m, marker)
654 low = false
657 low = true
658 -- YZ plane markers
659 for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
660 if low then
661 offset = -0.01
662 else
663 offset = 0.01
666 local marker = minetest.add_entity({x = x + offset, y = pos1.y + sizey - 0.5, z = pos1.z + sizez - 0.5}, "schemedit:display")
667 if marker ~= nil then
668 marker:set_properties({
669 visual_size={x=(sizez+0.01) * 2, y=sizey * 2},
671 marker:set_yaw(math.pi / 2)
672 marker:get_luaentity().id = id
673 marker:get_luaentity().owner = owner
674 table.insert(m, marker)
676 low = false
679 schemedit.markers[id] = m
680 return true
683 -- [function] Unmark region
684 function schemedit.unmark(pos)
685 local id = minetest.hash_node_position(pos)
686 if schemedit.markers[id] then
687 local retval
688 for _, entity in ipairs(schemedit.markers[id]) do
689 entity:remove()
690 retval = true
692 return retval
697 --- Mark node probability values near player
700 -- Show probability and force_place status of a particular position for player in HUD.
701 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
702 -- The distance to the node is also displayed below that. This can't be avoided and is
703 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
704 function schemedit.display_node_prob(player, pos, prob, force_place)
705 local wpstring
706 if prob and force_place == true then
707 wpstring = string.format("%d [F]", prob)
708 elseif prob then
709 wpstring = prob
710 elseif force_place == true then
711 wpstring = "[F]"
713 if wpstring then
714 return player:hud_add({
715 hud_elem_type = "waypoint",
716 name = wpstring,
717 text = "m", -- For the distance artifact
718 number = text_color_number,
719 world_pos = pos,
724 -- Display the node probabilities and force_place status of the nodes in a region.
725 -- By default, this is done for nodes near the player (distance: 5).
726 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
727 function schemedit.display_node_probs_region(player, pos1, pos2)
728 local playername = player:get_player_name()
729 local pos = vector.round(player:get_pos())
731 local dist = 5
732 -- Default: 5 nodes away from player in any direction
733 if not pos1 then
734 pos1 = vector.subtract(pos, dist)
736 if not pos2 then
737 pos2 = vector.add(pos, dist)
739 for x=pos1.x, pos2.x do
740 for y=pos1.y, pos2.y do
741 for z=pos1.z, pos2.z do
742 local checkpos = {x=x, y=y, z=z}
743 local nodehash = minetest.hash_node_position(checkpos)
745 -- If node is already displayed, remove it so it can re replaced later
746 if displayed_waypoints[playername][nodehash] then
747 player:hud_remove(displayed_waypoints[playername][nodehash])
748 displayed_waypoints[playername][nodehash] = nil
751 local prob, force_place
752 local meta = minetest.get_meta(checkpos)
753 prob = tonumber(meta:get_string("schemedit_prob"))
754 force_place = meta:get_string("schemedit_force_place") == "true"
755 local hud_id = schemedit.display_node_prob(player, checkpos, prob, force_place)
756 if hud_id then
757 displayed_waypoints[playername][nodehash] = hud_id
758 displayed_waypoints[playername].display_active = true
765 -- Remove all active displayed node statuses.
766 function schemedit.clear_displayed_node_probs(player)
767 local playername = player:get_player_name()
768 for nodehash, hud_id in pairs(displayed_waypoints[playername]) do
769 player:hud_remove(hud_id)
770 displayed_waypoints[playername][nodehash] = nil
771 displayed_waypoints[playername].display_active = false
775 minetest.register_on_joinplayer(function(player)
776 displayed_waypoints[player:get_player_name()] = {
777 display_active = false -- If true, there *might* be at least one active node prob HUD display
778 -- If false, no node probabilities are displayed for sure.
780 end)
782 minetest.register_on_leaveplayer(function(player)
783 displayed_waypoints[player:get_player_name()] = nil
784 end)
786 -- Regularily clear the displayed node probabilities and force_place
787 -- for all players who do not wield the probtool.
788 -- This makes sure the screen is not spammed with information when it
789 -- isn't needed.
790 local cleartimer = 0
791 minetest.register_globalstep(function(dtime)
792 cleartimer = cleartimer + dtime
793 if cleartimer > 2 then
794 local players = minetest.get_connected_players()
795 for p = 1, #players do
796 local player = players[p]
797 local pname = player:get_player_name()
798 if displayed_waypoints[pname].display_active then
799 local item = player:get_wielded_item()
800 if item:get_name() ~= "schemedit:probtool" then
801 schemedit.clear_displayed_node_probs(player)
805 cleartimer = 0
807 end)
810 --- Registrations
813 -- [priv] schematic_override
814 minetest.register_privilege("schematic_override", {
815 description = S("Allows you to access schemedit nodes not owned by you"),
816 give_to_singleplayer = false,
819 -- [node] Schematic creator
820 minetest.register_node("schemedit:creator", {
821 description = S("Schematic Creator"),
822 _doc_items_longdesc = S("The schematic creator is used to save a region of the world into a schematic file (.mts)."),
823 _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"..
824 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"..
825 S("The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.").."\n\n"..
826 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"..
827 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."),
828 tiles = {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
829 "schemedit_creator_sides.png"},
830 groups = { dig_immediate = 2},
831 paramtype2 = "facedir",
832 is_ground_content = false,
834 after_place_node = function(pos, player)
835 local name = player:get_player_name()
836 local meta = minetest.get_meta(pos)
838 meta:set_string("owner", name)
839 meta:set_string("infotext", S("Schematic Creator").."\n"..S("(owned by @1)", name))
840 meta:set_string("prob_list", minetest.serialize({}))
841 meta:set_string("slices", minetest.serialize({}))
843 local node = minetest.get_node(pos)
844 local dir = minetest.facedir_to_dir(node.param2)
846 meta:set_int("x_size", 1)
847 meta:set_int("y_size", 1)
848 meta:set_int("z_size", 1)
850 -- Don't take item from itemstack
851 return true
852 end,
853 can_dig = function(pos, player)
854 local name = player:get_player_name()
855 local meta = minetest.get_meta(pos)
856 if meta:get_string("owner") == name or
857 minetest.check_player_privs(player, "schematic_override") == true then
858 return true
861 return false
862 end,
863 on_rightclick = function(pos, node, player)
864 local meta = minetest.get_meta(pos)
865 local name = player:get_player_name()
866 if meta:get_string("owner") == name or
867 minetest.check_player_privs(player, "schematic_override") == true then
868 -- Get player attribute
869 local tab = player:get_attribute("schemedit:tab")
870 if not forms[tab] or not tab then
871 tab = "main"
874 schemedit.show_formspec(pos, player, tab, true)
876 end,
877 after_destruct = function(pos)
878 schemedit.unmark(pos)
879 end,
882 minetest.register_tool("schemedit:probtool", {
883 description = S("Schematic Node Probability Tool"),
884 _doc_items_longdesc =
885 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"..
886 S("It allows you to set two things:").."\n"..
887 S("1) Set probability: Chance for any particular node to be actually placed (default: always placed)").."\n"..
888 S("2) Enable force placement: These nodes replace node other than air and ignored when placed in a schematic (default: off)"),
889 _doc_items_usagehelp = "\n"..
890 S("BASIC USAGE:").."\n"..
891 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"..
892 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"..
893 S("NODE HUD:").."\n"..
894 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"..
895 S("To disable the node HUD, unselect the tool or hit “place” while not pointing anything.").."\n\n"..
896 S("UPDATING THE NODE HUD:").."\n"..
897 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."),
898 wield_image = "schemedit_probtool.png",
899 inventory_image = "schemedit_probtool.png",
900 liquids_pointable = true,
901 on_use = function(itemstack, user, pointed_thing)
902 local ctrl = user:get_player_control()
903 -- Simple use
904 if not ctrl.sneak then
905 -- Open dialog to change the probability to apply to nodes
906 schemedit.show_formspec(user:get_pos(), user, "probtool", true)
908 -- Use + sneak
909 else
910 -- Display the probability and force_place values for nodes.
912 -- If a schematic creator was punched, only enable display for all nodes
913 -- within the creator's region.
914 local use_creator_region = false
915 if pointed_thing and pointed_thing.type == "node" and pointed_thing.under then
916 punchpos = pointed_thing.under
917 local node = minetest.get_node(punchpos)
918 if node.name == "schemedit:creator" then
919 local pos1, pos2 = schemedit.size(punchpos)
920 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
921 schemedit.display_node_probs_region(user, pos1, pos2)
922 return
926 -- Otherwise, just display the region close to the player
927 schemedit.display_node_probs_region(user)
929 end,
930 on_secondary_use = function(itemstack, user, pointed_thing)
931 schemedit.clear_displayed_node_probs(user)
932 end,
933 -- Set note probability and force_place and enable node probability display
934 on_place = function(itemstack, placer, pointed_thing)
935 -- Use pointed node's on_rightclick function first, if present
936 local node = minetest.get_node(pointed_thing.under)
937 if placer and not placer:get_player_control().sneak then
938 if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
939 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
943 -- This sets the node probability of pointed node to the
944 -- currently used probability stored in the tool.
945 local pos = pointed_thing.under
946 local node = minetest.get_node(pos)
947 -- Schematic void are ignored, they always have probability 0
948 if node.name == "schemedit:void" then
949 return itemstack
951 local nmeta = minetest.get_meta(pos)
952 local imeta = itemstack:get_meta()
953 local prob = tonumber(imeta:get_string("schemedit_prob"))
954 local force_place = imeta:get_string("schemedit_force_place")
956 if not prob or prob == 255 then
957 nmeta:set_string("schemedit_prob", nil)
958 else
959 nmeta:set_string("schemedit_prob", prob)
961 if force_place == "true" then
962 nmeta:set_string("schemedit_force_place", "true")
963 else
964 nmeta:set_string("schemedit_force_place", nil)
967 -- Enable node probablity display
968 schemedit.display_node_probs_region(placer)
970 return itemstack
971 end,
974 minetest.register_node("schemedit:void", {
975 description = S("Schematic Void"),
976 _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."),
977 _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."),
978 tiles = { "schemedit_void.png" },
979 drawtype = "nodebox",
980 is_ground_content = false,
981 paramtype = "light",
982 walkable = false,
983 sunlight_propagates = true,
984 node_box = {
985 type = "fixed",
986 fixed = {
987 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
990 groups = { dig_immediate = 3},
993 -- [entity] Visible schematic border
994 minetest.register_entity("schemedit:display", {
995 visual = "upright_sprite",
996 textures = {"schemedit_border.png"},
997 visual_size = {x=10, y=10},
998 collisionbox = {0,0,0,0,0,0},
999 physical = false,
1001 on_step = function(self, dtime)
1002 if not self.id then
1003 self.object:remove()
1004 elseif not schemedit.markers[self.id] then
1005 self.object:remove()
1007 end,
1008 on_activate = function(self)
1009 self.object:set_armor_groups({immortal = 1})
1010 end,
1013 -- [chatcommand] Place schematic
1014 minetest.register_chatcommand("placeschem", {
1015 description = S("Place schematic at the position specified or the current player position (loaded from @1.)", export_path_full),
1016 privs = {debug = true},
1017 params = S("<schematic name>[.mts] [<x> <y> <z>]"),
1018 func = function(name, param)
1019 local schem, p = string.match(param, "^([^ ]+) *(.*)$")
1020 local pos = minetest.string_to_pos(p)
1022 if not schem then
1023 return false, S("No schematic file specified.")
1026 if not pos then
1027 pos = minetest.get_player_by_name(name):get_pos()
1030 -- Automatiically add file name suffix if omitted
1031 local schem_full
1032 if string.sub(schem, string.len(schem)-3, string.len(schem)) == ".mts" then
1033 schem_full = schem
1034 else
1035 schem_full = schem .. ".mts"
1038 local success = minetest.place_schematic(pos, export_path_full .. DIR_DELIM .. schem_full, "random", nil, false)
1040 if success == nil then
1041 return false, S("Schematic file could not be loaded!")
1042 else
1043 return true
1045 end,