Add screenshot
[minetest_schemedit.git] / init.lua
blobf1ce4768cc648421cc883ad6644d4871dd5ee2a6
1 local schemedit = {}
3 -- Directory delimeter fallback (normally comes from builtin)
4 if not DIR_DELIM then
5 DIR_DELIM = "/"
6 end
7 local export_path_full = table.concat({minetest.get_worldpath(), "schems"}, DIR_DELIM)
9 local text_color = "#D79E9E"
10 local text_color_number = 0xD79E9E
12 schemedit.markers = {}
14 -- [local function] Renumber table
15 local function renumber(t)
16 local res = {}
17 for _, i in pairs(t) do
18 res[#res + 1] = i
19 end
20 return res
21 end
23 ---
24 --- Formspec API
25 ---
27 local contexts = {}
28 local form_data = {}
29 local tabs = {}
30 local forms = {}
31 local displayed_waypoints = {}
33 -- Sadly, the probabilities presented in Lua (0-255) are not identical to the REAL probabilities in the
34 -- schematic file (0-127). There are two converter functions to convert from one probability type to another.
35 -- This mod tries to retain the “Lua probability” as long as possible and only switches to “schematic probability”
36 -- on an actual export to a schematic.
38 function schemedit.lua_prob_to_schematic_prob(lua_prob)
39 return math.floor(lua_prob / 2)
40 end
42 function schemedit.schematic_prob_to_lua_prob(schematic_prob)
43 return schematic_prob * 2
45 end
47 -- [function] Add form
48 function schemedit.add_form(name, def)
49 def.name = name
50 forms[name] = def
52 if def.tab then
53 tabs[#tabs + 1] = name
54 end
55 end
57 -- [function] Generate tabs
58 function schemedit.generate_tabs(current)
59 local retval = "tabheader[0,0;tabs;"
60 for _, t in pairs(tabs) do
61 local f = forms[t]
62 if f.tab ~= false and f.caption then
63 retval = retval..f.caption..","
65 if type(current) ~= "number" and current == f.name then
66 current = _
67 end
68 end
69 end
70 retval = retval:sub(1, -2) -- Strip last comma
71 retval = retval..";"..current.."]" -- Close tabheader
72 return retval
73 end
75 -- [function] Handle tabs
76 function schemedit.handle_tabs(pos, name, fields)
77 local tab = tonumber(fields.tabs)
78 if tab and tabs[tab] and forms[tabs[tab]] then
79 schemedit.show_formspec(pos, name, forms[tabs[tab]].name)
80 return true
81 end
82 end
84 -- [function] Show formspec
85 function schemedit.show_formspec(pos, player, tab, show, ...)
86 if forms[tab] then
87 if type(player) == "string" then
88 player = minetest.get_player_by_name(player)
89 end
90 local name = player:get_player_name()
92 if show ~= false then
93 if not form_data[name] then
94 form_data[name] = {}
95 end
97 local form = forms[tab].get(form_data[name], pos, name, ...)
98 if forms[tab].tab then
99 form = form..schemedit.generate_tabs(tab)
102 minetest.show_formspec(name, "schemedit:"..tab, form)
103 contexts[name] = pos
105 -- Update player attribute
106 if forms[tab].cache_name ~= false then
107 player:set_attribute("schemedit:tab", tab)
109 else
110 minetest.close_formspec(pname, "schemedit:"..tab)
115 -- [event] On receive fields
116 minetest.register_on_player_receive_fields(function(player, formname, fields)
117 local formname = formname:split(":")
119 if formname[1] == "schemedit" and forms[formname[2]] then
120 local handle = forms[formname[2]].handle
121 local name = player:get_player_name()
122 if contexts[name] then
123 if not form_data[name] then
124 form_data[name] = {}
127 if not schemedit.handle_tabs(contexts[name], name, fields) and handle then
128 handle(form_data[name], contexts[name], name, fields)
132 end)
134 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
135 schemedit.scan_metadata = function(pos1, pos2)
136 local prob_list = {}
138 for x=pos1.x, pos2.x do
139 for y=pos1.y, pos2.y do
140 for z=pos1.z, pos2.z do
141 local scanpos = {x=x, y=y, z=z}
142 local node = minetest.get_node_or_nil(scanpos)
144 local prob, force_place
145 if node == nil or node.name == "schemedit:void" then
146 prob = 0
147 force_place = false
148 else
149 local meta = minetest.get_meta(scanpos)
151 prob = tonumber(meta:get_string("schemedit_prob")) or 255
152 local fp = meta:get_string("schemedit_force_place")
153 if fp == "true" then
154 force_place = true
155 else
156 force_place = false
160 local hashpos = minetest.hash_node_position(scanpos)
161 prob_list[hashpos] = {
162 pos = scanpos,
163 prob = prob,
164 force_place = force_place,
170 return prob_list
173 -- Sets probability and force_place metadata of an item.
174 -- Also updates item description.
175 -- The itemstack is updated in-place.
176 local function set_item_metadata(itemstack, prob, force_place)
177 local smeta = itemstack:get_meta()
178 local prob_desc = "\nProbability: "..(prob) or
179 smeta:get_string("schemedit_prob") or "Not Set"
180 -- Update probability
181 if prob and prob >= 0 and prob < 255 then
182 smeta:set_string("schemedit_prob", tostring(prob))
183 elseif prob and prob == 255 then
184 -- Clear prob metadata for default probability
185 prob_desc = ""
186 smeta:set_string("schemedit_prob", nil)
187 else
188 prob_desc = "\nProbability: "..(smeta:get_string("schemedit_prob") or
189 "Not Set")
192 -- Update force place
193 if force_place == true then
194 smeta:set_string("schemedit_force_place", "true")
195 elseif force_place == false then
196 smeta:set_string("schemedit_force_place", nil)
199 -- Update description
200 local desc = minetest.registered_items[itemstack:get_name()].description
201 local meta_desc = smeta:get_string("description")
202 if meta_desc and meta_desc ~= "" then
203 desc = meta_desc
206 local original_desc = smeta:get_string("original_description")
207 if original_desc and original_desc ~= "" then
208 desc = original_desc
209 else
210 smeta:set_string("original_description", desc)
213 local force_desc = ""
214 if smeta:get_string("schemedit_force_place") == "true" then
215 force_desc = "\n".."Force placement"
218 desc = desc..minetest.colorize(text_color, prob_desc..force_desc)
220 smeta:set_string("description", desc)
222 return itemstack
226 --- Formspec Tabs
229 schemedit.add_form("main", {
230 tab = true,
231 caption = "Main",
232 get = function(self, pos, name)
233 local meta = minetest.get_meta(pos):to_table().fields
234 local strpos = minetest.pos_to_string(pos)
235 local hashpos = minetest.hash_node_position(pos)
237 local border_button
238 if meta.schem_border == "true" and schemedit.markers[hashpos] then
239 border_button = "button[3.5,7.5;3,1;border;Hide border]"
240 else
241 border_button = "button[3.5,7.5;3,1;border;Show border]"
244 -- TODO: Show information regarding volume, pos1, pos2, etc... in formspec
245 local form = [[
246 size[7,8]
247 label[0.5,-0.1;Position: ]]..strpos..[[]
248 label[3,-0.1;Owner: ]]..name..[[]
250 field[0.8,1;5,1;name;Schematic name:;]]..minetest.formspec_escape(meta.schem_name or "")..[[]
251 button[5.3,0.69;1.2,1;save_name;Save]
252 tooltip[save_name;Save schematic name]
253 field_close_on_enter[name;false]
255 button[0.5,1.5;6,1;export;Export schematic]
256 textarea[0.8,2.5;6.2,5;;The schematic will be exported as a .mts file and stored in]]..
257 "\n" .. export_path_full .. DIR_DELIM .. [[<name>.mts.;]
258 field[0.8,7;2,1;x;X size:;]]..meta.x_size..[[]
259 field[2.8,7;2,1;y;Y size:;]]..meta.y_size..[[]
260 field[4.8,7;2,1;z;Z size:;]]..meta.z_size..[[]
261 field_close_on_enter[x;false]
262 field_close_on_enter[y;false]
263 field_close_on_enter[z;false]
265 button[0.5,7.5;3,1;save;Save size]
266 ]]..
267 border_button
268 if minetest.get_modpath("doc") then
269 form = form .. "image_button[6.4,-0.2;0.8,0.8;doc_button_icon_lores.png;doc;]" ..
270 "tooltip[doc;Help]"
272 return form
273 end,
274 handle = function(self, pos, name, fields)
275 local realmeta = minetest.get_meta(pos)
276 local meta = realmeta:to_table().fields
277 local hashpos = minetest.hash_node_position(pos)
279 if fields.doc then
280 doc.show_entry(name, "nodes", "schemedit:creator", true)
281 return
284 -- Toggle border
285 if fields.border then
286 if meta.schem_border == "true" and schemedit.markers[hashpos] then
287 schemedit.unmark(pos)
288 meta.schem_border = "false"
289 else
290 schemedit.mark(pos)
291 meta.schem_border = "true"
295 -- Save size vector values
296 if (fields.save or fields.key_enter_field == "x" or
297 fields.key_enter_field == "y" or fields.key_enter_field == "z")
298 and (fields.x and fields.y and fields.z and fields.x ~= ""
299 and fields.y ~= "" and fields.z ~= "") then
300 local x, y, z = tonumber(fields.x), tonumber(fields.y), tonumber(fields.z)
302 if x then
303 meta.x_size = math.max(x, 1)
305 if y then
306 meta.y_size = math.max(y, 1)
308 if z then
309 meta.z_size = math.max(z, 1)
313 -- Save schematic name
314 if fields.save_name or fields.key_enter_field == "name" and fields.name and
315 fields.name ~= "" then
316 meta.schem_name = fields.name
319 -- Export schematic
320 if fields.export and meta.schem_name and meta.schem_name ~= "" then
321 local pos1, pos2 = schemedit.size(pos)
322 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
323 local path = export_path_full .. DIR_DELIM
324 minetest.mkdir(path)
326 local plist = schemedit.scan_metadata(pos1, pos2)
327 local probability_list = {}
328 for hash, i in pairs(plist) do
329 local prob = schemedit.lua_prob_to_schematic_prob(i.prob)
330 if i.force_place == true then
331 prob = prob + 128
334 table.insert(probability_list, {
335 pos = minetest.get_position_from_hash(hash),
336 prob = prob,
340 local slist = minetest.deserialize(meta.slices)
341 local slice_list = {}
342 for _, i in pairs(slist) do
343 slice_list[#slice_list + 1] = {
344 ypos = pos.y + i.ypos,
345 prob = schemedit.lua_prob_to_schematic_prob(i.prob),
349 local filepath = path..meta.schem_name..".mts"
350 local res = minetest.create_schematic(pos1, pos2, probability_list, filepath, slice_list)
352 if res then
353 minetest.chat_send_player(name, minetest.colorize("#00ff00",
354 "Exported schematic to "..filepath))
355 else
356 minetest.chat_send_player(name, minetest.colorize("red",
357 "Failed to export schematic to "..filepath))
361 -- Save meta before updating visuals
362 local inv = realmeta:get_inventory():get_lists()
363 realmeta:from_table({fields = meta, inventory = inv})
365 -- Update border
366 if not fields.border and meta.schem_border == "true" then
367 schemedit.mark(pos)
370 -- Update formspec
371 if not fields.quit then
372 schemedit.show_formspec(pos, minetest.get_player_by_name(name), "main")
374 end,
377 schemedit.add_form("slice", {
378 caption = "Y Slices",
379 tab = true,
380 get = function(self, pos, name, visible_panel)
381 local meta = minetest.get_meta(pos):to_table().fields
383 self.selected = self.selected or 1
384 local selected = tostring(self.selected)
385 local slice_list = minetest.deserialize(meta.slices)
386 local slices = ""
387 for _, i in pairs(slice_list) do
388 local insert = "Y = "..tostring(i.ypos).."; Probability = "..tostring(i.prob)
389 slices = slices..minetest.formspec_escape(insert)..","
391 slices = slices:sub(1, -2) -- Remove final comma
393 local form = [[
394 size[7,8]
395 table[0,0;6.8,6;slices;]]..slices..[[;]]..selected..[[]
398 if self.panel_add or self.panel_edit then
399 local ypos_default, prob_default = "", ""
400 local done_button = "button[5,7.18;2,1;done_add;Done]"
401 if self.panel_edit then
402 done_button = "button[5,7.18;2,1;done_edit;Done]"
403 if slice_list[self.selected] then
404 ypos_default = slice_list[self.selected].ypos
405 prob_default = slice_list[self.selected].prob
409 form = form..[[
410 field[0.3,7.5;2.5,1;ypos;Y position (max. ]]..(meta.y_size - 1)..[[):;]]..ypos_default..[[]
411 field[2.8,7.5;2.5,1;prob;Probability (0-255):;]]..prob_default..[[]
412 field_close_on_enter[ypos;false]
413 field_close_on_enter[prob;false]
414 ]]..done_button
417 if not self.panel_edit then
418 form = form.."button[0,6;2,1;add;+ Add slice]"
421 if slices ~= "" and self.selected and not self.panel_add then
422 if not self.panel_edit then
423 form = form..[[
424 button[2,6;2,1;remove;- Remove slice]
425 button[4,6;2,1;edit;+/- Edit slice]
427 else
428 form = form..[[
429 button[2,6;2,1;remove;- Remove slice]
430 button[4,6;2,1;edit;+/- Edit slice]
435 return form
436 end,
437 handle = function(self, pos, name, fields)
438 local meta = minetest.get_meta(pos)
439 local player = minetest.get_player_by_name(name)
441 if fields.slices then
442 local slices = fields.slices:split(":")
443 self.selected = tonumber(slices[2])
446 if fields.add then
447 if not self.panel_add then
448 self.panel_add = true
449 schemedit.show_formspec(pos, player, "slice")
450 else
451 self.panel_add = nil
452 schemedit.show_formspec(pos, player, "slice")
456 local ypos, prob = tonumber(fields.ypos), tonumber(fields.prob)
457 if (fields.done_add or fields.done_edit) and fields.ypos and fields.prob and
458 fields.ypos ~= "" and fields.prob ~= "" and ypos and prob and
459 ypos <= (meta:get_int("y_size") - 1) and prob >= 0 and prob <= 255 then
460 local slice_list = minetest.deserialize(meta:get_string("slices"))
461 local index = #slice_list + 1
462 if fields.done_edit then
463 index = self.selected
466 slice_list[index] = {ypos = ypos, prob = prob}
468 meta:set_string("slices", minetest.serialize(slice_list))
470 -- Update and show formspec
471 self.panel_add = nil
472 schemedit.show_formspec(pos, player, "slice")
475 if fields.remove and self.selected then
476 local slice_list = minetest.deserialize(meta:get_string("slices"))
477 slice_list[self.selected] = nil
478 meta:set_string("slices", minetest.serialize(renumber(slice_list)))
480 -- Update formspec
481 self.selected = 1
482 self.panel_edit = nil
483 schemedit.show_formspec(pos, player, "slice")
486 if fields.edit then
487 if not self.panel_edit then
488 self.panel_edit = true
489 schemedit.show_formspec(pos, player, "slice")
490 else
491 self.panel_edit = nil
492 schemedit.show_formspec(pos, player, "slice")
495 end,
498 schemedit.add_form("probtool", {
499 cache_name = false,
500 caption = "Schematic Node Probability Tool",
501 get = function(self, pos, name)
502 local player = minetest.get_player_by_name(name)
503 if not player then
504 return
506 local probtool = player:get_wielded_item()
507 if probtool:get_name() ~= "schemedit:probtool" then
508 return
511 local meta = probtool:get_meta()
512 local prob = tonumber(meta:get_string("schemedit_prob"))
513 local force_place = meta:get_string("schemedit_force_place")
515 if not prob then
516 prob = 255
518 if force_place == nil or force_place == "" then
519 force_place = "false"
521 local form = "size[5,4]"..
522 "label[0,0;Schematic Node Probability Tool]"..
523 "field[0.75,1;4,1;prob;Probability (0-255);"..prob.."]"..
524 "checkbox[0.60,1.5;force_place;Force placement;" .. force_place .. "]" ..
525 "button_exit[0.25,3;2,1;cancel;Cancel]"..
526 "button_exit[2.75,3;2,1;submit;Apply]"..
527 "tooltip[prob;Probability that the node will be placed]"..
528 "tooltip[force_place;If enabled, the node will replace nodes other than air and ignore]"..
529 "field_close_on_enter[prob;false]"
530 return form
531 end,
532 handle = function(self, pos, name, fields)
533 if fields.submit then
534 local prob = tonumber(fields.prob)
535 if prob then
536 local player = minetest.get_player_by_name(name)
537 if not player then
538 return
540 local probtool = player:get_wielded_item()
541 if probtool:get_name() ~= "schemedit:probtool" then
542 return
545 local force_place = self.force_place == true
547 set_item_metadata(probtool, prob, force_place)
549 player:set_wielded_item(probtool)
552 if fields.force_place == "true" then
553 self.force_place = true
554 elseif fields.force_place == "false" then
555 self.force_place = false
557 end,
561 --- API
564 --- Copies and modifies positions `pos1` and `pos2` so that each component of
565 -- `pos1` is less than or equal to the corresponding component of `pos2`.
566 -- Returns the new positions.
567 function schemedit.sort_pos(pos1, pos2)
568 if not pos1 or not pos2 then
569 return
572 pos1, pos2 = table.copy(pos1), table.copy(pos2)
573 if pos1.x > pos2.x then
574 pos2.x, pos1.x = pos1.x, pos2.x
576 if pos1.y > pos2.y then
577 pos2.y, pos1.y = pos1.y, pos2.y
579 if pos1.z > pos2.z then
580 pos2.z, pos1.z = pos1.z, pos2.z
582 return pos1, pos2
585 -- [function] Prepare size
586 function schemedit.size(pos)
587 local pos1 = vector.new(pos)
588 local meta = minetest.get_meta(pos)
589 local node = minetest.get_node(pos)
590 local param2 = node.param2
591 local size = {
592 x = meta:get_int("x_size"),
593 y = math.max(meta:get_int("y_size") - 1, 0),
594 z = meta:get_int("z_size"),
597 if param2 == 1 then
598 local new_pos = vector.add({x = size.z, y = size.y, z = -size.x}, pos)
599 pos1.x = pos1.x + 1
600 new_pos.z = new_pos.z + 1
601 return pos1, new_pos
602 elseif param2 == 2 then
603 local new_pos = vector.add({x = -size.x, y = size.y, z = -size.z}, pos)
604 pos1.z = pos1.z - 1
605 new_pos.x = new_pos.x + 1
606 return pos1, new_pos
607 elseif param2 == 3 then
608 local new_pos = vector.add({x = -size.z, y = size.y, z = size.x}, pos)
609 pos1.x = pos1.x - 1
610 new_pos.z = new_pos.z - 1
611 return pos1, new_pos
612 else
613 local new_pos = vector.add(size, pos)
614 pos1.z = pos1.z + 1
615 new_pos.x = new_pos.x - 1
616 return pos1, new_pos
620 -- [function] Mark region
621 function schemedit.mark(pos)
622 schemedit.unmark(pos)
624 local id = minetest.hash_node_position(pos)
625 local owner = minetest.get_meta(pos):get_string("owner")
626 local pos1, pos2 = schemedit.size(pos)
627 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
629 local thickness = 0.2
630 local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
631 local m = {}
632 local low = true
633 local offset
635 -- XY plane markers
636 for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
637 if low then
638 offset = -0.01
639 else
640 offset = 0.01
642 local marker = minetest.add_entity({x = pos1.x + sizex - 0.5, y = pos1.y + sizey - 0.5, z = z + offset}, "schemedit:display")
643 if marker ~= nil then
644 marker:set_properties({
645 visual_size={x=(sizex+0.01) * 2, y=sizey * 2},
647 marker:get_luaentity().id = id
648 marker:get_luaentity().owner = owner
649 table.insert(m, marker)
651 low = false
654 low = true
655 -- YZ plane markers
656 for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
657 if low then
658 offset = -0.01
659 else
660 offset = 0.01
663 local marker = minetest.add_entity({x = x + offset, y = pos1.y + sizey - 0.5, z = pos1.z + sizez - 0.5}, "schemedit:display")
664 if marker ~= nil then
665 marker:set_properties({
666 visual_size={x=(sizez+0.01) * 2, y=sizey * 2},
668 marker:set_yaw(math.pi / 2)
669 marker:get_luaentity().id = id
670 marker:get_luaentity().owner = owner
671 table.insert(m, marker)
673 low = false
676 schemedit.markers[id] = m
677 return true
680 -- [function] Unmark region
681 function schemedit.unmark(pos)
682 local id = minetest.hash_node_position(pos)
683 if schemedit.markers[id] then
684 local retval
685 for _, entity in ipairs(schemedit.markers[id]) do
686 entity:remove()
687 retval = true
689 return retval
694 --- Mark node probability values near player
697 -- Show probability and force_place status of a particular position for player in HUD.
698 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
699 -- The distance to the node is also displayed below that. This can't be avoided and is
700 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
701 function schemedit.display_node_prob(player, pos, prob, force_place)
702 local wpstring
703 if prob and force_place == true then
704 wpstring = string.format("%d [F]", prob)
705 elseif prob then
706 wpstring = prob
707 elseif force_place == true then
708 wpstring = "[F]"
710 if wpstring then
711 return player:hud_add({
712 hud_elem_type = "waypoint",
713 name = wpstring,
714 text = "m", -- For the distance artifact
715 number = text_color_number,
716 world_pos = pos,
721 -- Display the node probabilities and force_place status of the nodes in a region.
722 -- By default, this is done for nodes near the player (distance: 5).
723 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
724 function schemedit.display_node_probs_region(player, pos1, pos2)
725 local playername = player:get_player_name()
726 local pos = vector.round(player:getpos())
728 local dist = 5
729 -- Default: 5 nodes away from player in any direction
730 if not pos1 then
731 pos1 = vector.subtract(pos, dist)
733 if not pos2 then
734 pos2 = vector.add(pos, dist)
736 for x=pos1.x, pos2.x do
737 for y=pos1.y, pos2.y do
738 for z=pos1.z, pos2.z do
739 local checkpos = {x=x, y=y, z=z}
740 local nodehash = minetest.hash_node_position(checkpos)
742 -- If node is already displayed, remove it so it can re replaced later
743 if displayed_waypoints[playername][nodehash] then
744 player:hud_remove(displayed_waypoints[playername][nodehash])
745 displayed_waypoints[playername][nodehash] = nil
748 local prob, force_place
749 local meta = minetest.get_meta(checkpos)
750 prob = tonumber(meta:get_string("schemedit_prob"))
751 force_place = meta:get_string("schemedit_force_place") == "true"
752 local hud_id = schemedit.display_node_prob(player, checkpos, prob, force_place)
753 if hud_id then
754 displayed_waypoints[playername][nodehash] = hud_id
755 displayed_waypoints[playername].display_active = true
762 -- Remove all active displayed node statuses.
763 function schemedit.clear_displayed_node_probs(player)
764 local playername = player:get_player_name()
765 for nodehash, hud_id in pairs(displayed_waypoints[playername]) do
766 player:hud_remove(hud_id)
767 displayed_waypoints[playername][nodehash] = nil
768 displayed_waypoints[playername].display_active = false
772 minetest.register_on_joinplayer(function(player)
773 displayed_waypoints[player:get_player_name()] = {
774 display_active = false -- If true, there *might* be at least one active node prob HUD display
775 -- If false, no node probabilities are displayed for sure.
777 end)
779 minetest.register_on_leaveplayer(function(player)
780 displayed_waypoints[player:get_player_name()] = nil
781 end)
783 -- Regularily clear the displayed node probabilities and force_place
784 -- for all players who do not wield the probtool.
785 -- This makes sure the screen is not spammed with information when it
786 -- isn't needed.
787 local cleartimer = 0
788 minetest.register_globalstep(function(dtime)
789 cleartimer = cleartimer + dtime
790 if cleartimer > 2 then
791 local players = minetest.get_connected_players()
792 for p = 1, #players do
793 local player = players[p]
794 local pname = player:get_player_name()
795 if displayed_waypoints[pname].display_active then
796 local item = player:get_wielded_item()
797 if item:get_name() ~= "schemedit:probtool" then
798 schemedit.clear_displayed_node_probs(player)
802 cleartimer = 0
804 end)
807 --- Registrations
810 -- [priv] schematic_override
811 minetest.register_privilege("schematic_override", {
812 description = "Allows you to access schemedit nodes not owned by you",
813 give_to_singleplayer = false,
816 -- [node] Schematic creator
817 minetest.register_node("schemedit:creator", {
818 description = "Schematic Creator",
819 _doc_items_longdesc = "The schematic creator is used to save a region of the world into a schematic file (.mts).",
820 _doc_items_usagehelp = "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"..
821 "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"..
822 "The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.".."\n\n"..
823 "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"..
824 "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.",
825 tiles = {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
826 "schemedit_creator_sides.png"},
827 groups = { dig_immediate = 2},
828 paramtype2 = "facedir",
829 is_ground_content = false,
831 after_place_node = function(pos, player)
832 local name = player:get_player_name()
833 local meta = minetest.get_meta(pos)
835 meta:set_string("owner", name)
836 meta:set_string("infotext", "Schematic Creator\n(owned by "..name..")")
837 meta:set_string("prob_list", minetest.serialize({}))
838 meta:set_string("slices", minetest.serialize({}))
840 local node = minetest.get_node(pos)
841 local dir = minetest.facedir_to_dir(node.param2)
843 meta:set_int("x_size", 1)
844 meta:set_int("y_size", 1)
845 meta:set_int("z_size", 1)
847 -- Don't take item from itemstack
848 return true
849 end,
850 can_dig = function(pos, player)
851 local name = player:get_player_name()
852 local meta = minetest.get_meta(pos)
853 if meta:get_string("owner") == name or
854 minetest.check_player_privs(player, "schematic_override") == true then
855 return true
858 return false
859 end,
860 on_rightclick = function(pos, node, player)
861 local meta = minetest.get_meta(pos)
862 local name = player:get_player_name()
863 if meta:get_string("owner") == name or
864 minetest.check_player_privs(player, "schematic_override") == true then
865 -- Get player attribute
866 local tab = player:get_attribute("schemedit:tab")
867 if not forms[tab] or not tab then
868 tab = "main"
871 schemedit.show_formspec(pos, player, tab, true)
873 end,
874 after_destruct = function(pos)
875 schemedit.unmark(pos)
876 end,
879 minetest.register_tool("schemedit:probtool", {
880 description = "Schematic Node Probability Tool",
881 _doc_items_longdesc =
882 "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"..
883 "It allows you to set two things:".."\n"..
884 "1) Set probability: Chance for any particular node to be actually placed (default: always placed)".."\n"..
885 "2) Enable force placement: These nodes replace node other than air and ignored when placed in a schematic (default: off)",
886 _doc_items_usagehelp = "\n"..
887 "BASIC USAGE:".."\n"..
888 "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"..
889 "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"..
890 "NODE HUD:".."\n"..
891 "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"..
892 "To disable the node HUD, unselect the tool or hit “place” while not pointing anything.".."\n\n"..
893 "UPDATING THE NODE HUD:".."\n"..
894 "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.",
895 wield_image = "schemedit_probtool.png",
896 inventory_image = "schemedit_probtool.png",
897 liquids_pointable = true,
898 on_use = function(itemstack, user, pointed_thing)
899 local ctrl = user:get_player_control()
900 -- Simple use
901 if not ctrl.sneak then
902 -- Open dialog to change the probability to apply to nodes
903 schemedit.show_formspec(user:getpos(), user, "probtool", true)
905 -- Use + sneak
906 else
907 -- Display the probability and force_place values for nodes.
909 -- If a schematic creator was punched, only enable display for all nodes
910 -- within the creator's region.
911 local use_creator_region = false
912 if pointed_thing and pointed_thing.type == "node" and pointed_thing.under then
913 punchpos = pointed_thing.under
914 local node = minetest.get_node(punchpos)
915 if node.name == "schemedit:creator" then
916 local pos1, pos2 = schemedit.size(punchpos)
917 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
918 schemedit.display_node_probs_region(user, pos1, pos2)
919 return
923 -- Otherwise, just display the region close to the player
924 schemedit.display_node_probs_region(user)
926 end,
927 on_secondary_use = function(itemstack, user, pointed_thing)
928 schemedit.clear_displayed_node_probs(user)
929 end,
930 -- Set note probability and force_place and enable node probability display
931 on_place = function(itemstack, placer, pointed_thing)
932 -- Use pointed node's on_rightclick function first, if present
933 local node = minetest.get_node(pointed_thing.under)
934 if placer and not placer:get_player_control().sneak then
935 if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
936 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
940 -- This sets the node probability of pointed node to the
941 -- currently used probability stored in the tool.
942 local pos = pointed_thing.under
943 local node = minetest.get_node(pos)
944 -- Schematic void are ignored, they always have probability 0
945 if node.name == "schemedit:void" then
946 return itemstack
948 local nmeta = minetest.get_meta(pos)
949 local imeta = itemstack:get_meta()
950 local prob = tonumber(imeta:get_string("schemedit_prob"))
951 local force_place = imeta:get_string("schemedit_force_place")
953 if not prob or prob == 255 then
954 nmeta:set_string("schemedit_prob", nil)
955 else
956 nmeta:set_string("schemedit_prob", prob)
958 if force_place == "true" then
959 nmeta:set_string("schemedit_force_place", "true")
960 else
961 nmeta:set_string("schemedit_force_place", nil)
964 -- Enable node probablity display
965 schemedit.display_node_probs_region(placer)
967 return itemstack
968 end,
971 minetest.register_node("schemedit:void", {
972 description = "Schematic Void",
973 _doc_items_longdesc = "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.",
974 _doc_items_usagehelp = "Just place the schematic void like any other block and use the schematic creator to save a portion of the world.",
975 tiles = { "schemedit_void.png" },
976 drawtype = "nodebox",
977 is_ground_content = false,
978 paramtype = "light",
979 walkable = false,
980 sunlight_propagates = true,
981 node_box = {
982 type = "fixed",
983 fixed = {
984 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
987 groups = { dig_immediate = 3},
990 -- [entity] Visible schematic border
991 minetest.register_entity("schemedit:display", {
992 visual = "upright_sprite",
993 textures = {"schemedit_border.png"},
994 visual_size = {x=10, y=10},
995 collisionbox = {0,0,0,0,0,0},
996 physical = false,
998 on_step = function(self, dtime)
999 if not self.id then
1000 self.object:remove()
1001 elseif not schemedit.markers[self.id] then
1002 self.object:remove()
1004 end,
1005 on_activate = function(self)
1006 self.object:set_armor_groups({immortal = 1})
1007 end,
1010 -- [chatcommand] Place schematic
1011 minetest.register_chatcommand("placeschem", {
1012 description = "Place schematic at the position specified or the current "..
1013 "player position (loaded from "..export_path_full..".",
1014 privs = {debug = true},
1015 params = "<schematic name>[.mts] [<x> <y> <z>]",
1016 func = function(name, param)
1017 local schem, p = string.match(param, "^([^ ]+) *(.*)$")
1018 local pos = minetest.string_to_pos(p)
1020 if not schem then
1021 return false, "No schematic file specified."
1024 if not pos then
1025 pos = minetest.get_player_by_name(name):get_pos()
1028 -- Automatiically add file name suffix if omitted
1029 local schem_full
1030 if string.sub(schem, string.len(schem)-3, string.len(schem)) == ".mts" then
1031 schem_full = schem
1032 else
1033 schem_full = schem .. ".mts"
1036 local success = minetest.place_schematic(pos, export_path_full .. DIR_DELIM .. schem_full, "random", nil, false)
1038 if success == nil then
1039 return false, "Schematic file could not be loaded!"
1040 else
1041 return true
1043 end,