Refactor code and fix bugs
[minetest_schemedit.git] / init.lua
blob803517232e9c3e0e11620a39e3efa6691c5ade66
1 -- advschem/init.lua
3 local advschem = {}
5 -- Directory delimeter fallback (normally comes from builtin)
6 if not DIR_DELIM then
7 DIR_DELIM = "/"
8 end
9 local export_path_full = table.concat({minetest.get_worldpath(), "schems"}, DIR_DELIM)
11 local text_color = "#D79E9E"
12 local text_color_number = 0xD79E9E
14 advschem.markers = {}
16 -- [local function] Renumber table
17 local function renumber(t)
18 local res = {}
19 for _, i in pairs(t) do
20 res[#res + 1] = i
21 end
22 return res
23 end
25 ---
26 --- Formspec API
27 ---
29 local contexts = {}
30 local form_data = {}
31 local tabs = {}
32 local forms = {}
33 local displayed_waypoints = {}
35 -- [function] Add form
36 function advschem.add_form(name, def)
37 def.name = name
38 forms[name] = def
40 if def.tab then
41 tabs[#tabs + 1] = name
42 end
43 end
45 -- [function] Generate tabs
46 function advschem.generate_tabs(current)
47 local retval = "tabheader[0,0;tabs;"
48 for _, t in pairs(tabs) do
49 local f = forms[t]
50 if f.tab ~= false and f.caption then
51 retval = retval..f.caption..","
53 if type(current) ~= "number" and current == f.name then
54 current = _
55 end
56 end
57 end
58 retval = retval:sub(1, -2) -- Strip last comma
59 retval = retval..";"..current.."]" -- Close tabheader
60 return retval
61 end
63 -- [function] Handle tabs
64 function advschem.handle_tabs(pos, name, fields)
65 local tab = tonumber(fields.tabs)
66 if tab and tabs[tab] and forms[tabs[tab]] then
67 advschem.show_formspec(pos, name, forms[tabs[tab]].name)
68 return true
69 end
70 end
72 -- [function] Show formspec
73 function advschem.show_formspec(pos, player, tab, show, ...)
74 if forms[tab] then
75 if type(player) == "string" then
76 player = minetest.get_player_by_name(player)
77 end
78 local name = player:get_player_name()
80 if show ~= false then
81 if not form_data[name] then
82 form_data[name] = {}
83 end
85 local form = forms[tab].get(form_data[name], pos, name, ...)
86 if forms[tab].tab then
87 form = form..advschem.generate_tabs(tab)
88 end
90 minetest.show_formspec(name, "advschem:"..tab, form)
91 contexts[name] = pos
93 -- Update player attribute
94 if forms[tab].cache_name ~= false then
95 player:set_attribute("advschem:tab", tab)
96 end
97 else
98 minetest.close_formspec(pname, "advschem:"..tab)
99 end
103 -- [event] On receive fields
104 minetest.register_on_player_receive_fields(function(player, formname, fields)
105 local formname = formname:split(":")
107 if formname[1] == "advschem" and forms[formname[2]] then
108 local handle = forms[formname[2]].handle
109 local name = player:get_player_name()
110 if contexts[name] then
111 if not form_data[name] then
112 form_data[name] = {}
115 if not advschem.handle_tabs(contexts[name], name, fields) and handle then
116 handle(form_data[name], contexts[name], name, fields)
120 end)
122 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
123 advschem.scan_metadata = function(pos1, pos2)
124 local prob_list = {}
126 for x=pos1.x, pos2.x do
127 for y=pos1.y, pos2.y do
128 for z=pos1.z, pos2.z do
129 local scanpos = {x=x, y=y, z=z}
130 local node = minetest.get_node_or_nil(scanpos)
132 local prob, force_place
133 if node == nil or node.name == "advschem:void" then
134 prob = 0
135 force_place = false
136 else
137 local meta = minetest.get_meta(scanpos)
139 prob = tonumber(meta:get_string("advschem_prob")) or 127
140 local fp = meta:get_string("advschem_force_place")
141 if fp == "true" then
142 force_place = true
143 else
144 force_place = false
148 local ostrpos = minetest.pos_to_string(scanpos)
149 prob_list[ostrpos] = {
150 pos = scanpos,
151 prob = prob,
152 force_place = force_place,
158 return prob_list
161 -- Sets probability and force_place metadata of an item.
162 -- Also updates item description.
163 -- The itemstack is updated in-place.
164 local function set_item_metadata(itemstack, prob, force_place)
165 local smeta = itemstack:get_meta()
166 local prob_desc = "\nProbability: "..(prob) or
167 smeta:get_string("advschem_prob") or "Not Set"
168 -- Update probability
169 if prob and prob >= 0 and prob < 127 then
170 smeta:set_string("advschem_prob", tostring(prob))
171 elseif prob and prob == 127 then
172 -- Clear prob metadata for default probability
173 prob_desc = ""
174 smeta:set_string("advschem_prob", nil)
175 else
176 prob_desc = "\nProbability: "..(smeta:get_string("advschem_prob") or
177 "Not Set")
180 -- Update force place
181 if force_place == true then
182 smeta:set_string("advschem_force_place", "true")
183 elseif force_place == false then
184 smeta:set_string("advschem_force_place", nil)
187 -- Update description
188 local desc = minetest.registered_items[itemstack:get_name()].description
189 local meta_desc = smeta:get_string("description")
190 if meta_desc and meta_desc ~= "" then
191 desc = meta_desc
194 local original_desc = smeta:get_string("original_description")
195 if original_desc and original_desc ~= "" then
196 desc = original_desc
197 else
198 smeta:set_string("original_description", desc)
201 local force_desc = ""
202 if smeta:get_string("advschem_force_place") == "true" then
203 force_desc = "\n".."Force placement"
206 desc = desc..minetest.colorize(text_color, prob_desc..force_desc)
208 smeta:set_string("description", desc)
210 return itemstack
214 --- Formspec Tabs
217 advschem.add_form("main", {
218 tab = true,
219 caption = "Main",
220 get = function(self, pos, name)
221 local meta = minetest.get_meta(pos):to_table().fields
222 local strpos = minetest.pos_to_string(pos)
224 local border_button
225 if meta.schem_border == "true" and advschem.markers[strpos] then
226 border_button = "button[3.5,7.5;3,1;border;Hide border]"
227 else
228 border_button = "button[3.5,7.5;3,1;border;Show border]"
231 -- TODO: Show information regarding volume, pos1, pos2, etc... in formspec
232 return [[
233 size[7,8]
234 label[0.5,-0.1;Position: ]]..strpos..[[]
235 label[3,-0.1;Owner: ]]..name..[[]
237 field[0.8,1;5,1;name;Schematic name:;]]..minetest.formspec_escape(meta.schem_name or "")..[[]
238 button[5.3,0.69;1.2,1;save_name;Save]
239 tooltip[save_name;Save schematic name]
240 field_close_on_enter[name;false]
242 button[0.5,1.5;6,1;export;Export schematic]
243 textarea[0.8,2.5;6.2,5;;The schematic will be exported as a .mts file and stored in]]..
244 "\n" .. export_path_full .. DIR_DELIM .. [[<name>.mts.;]
245 field[0.8,7;2,1;x;X size:;]]..meta.x_size..[[]
246 field[2.8,7;2,1;y;Y size:;]]..meta.y_size..[[]
247 field[4.8,7;2,1;z;Z size:;]]..meta.z_size..[[]
248 field_close_on_enter[x;false]
249 field_close_on_enter[y;false]
250 field_close_on_enter[z;false]
252 button[0.5,7.5;3,1;save;Save size]
253 ]]..
254 border_button
255 end,
256 handle = function(self, pos, name, fields)
257 local realmeta = minetest.get_meta(pos)
258 local meta = realmeta:to_table().fields
259 local strpos = minetest.pos_to_string(pos)
261 -- Toggle border
262 if fields.border then
263 if meta.schem_border == "true" and advschem.markers[strpos] then
264 advschem.unmark(pos)
265 meta.schem_border = "false"
266 else
267 advschem.mark(pos)
268 meta.schem_border = "true"
272 -- Save size vector values
273 if (fields.save or fields.key_enter_field == "x" or
274 fields.key_enter_field == "y" or fields.key_enter_field == "z")
275 and (fields.x and fields.y and fields.z and fields.x ~= ""
276 and fields.y ~= "" and fields.z ~= "") then
277 local x, y, z = tonumber(fields.x), tonumber(fields.y), tonumber(fields.z)
279 if x then
280 meta.x_size = math.max(x, 1)
282 if y then
283 meta.y_size = math.max(y, 1)
285 if z then
286 meta.z_size = math.max(z, 1)
290 -- Save schematic name
291 if fields.save_name or fields.key_enter_field == "name" and fields.name and
292 fields.name ~= "" then
293 meta.schem_name = fields.name
296 -- Export schematic
297 if fields.export and meta.schem_name and meta.schem_name ~= "" then
298 local pos1, pos2 = advschem.size(pos)
299 pos1, pos2 = advschem.sort_pos(pos1, pos2)
300 local path = export_path_full .. DIR_DELIM
301 minetest.mkdir(path)
303 local plist = advschem.scan_metadata(pos1, pos2)
304 local probability_list = {}
305 for _, i in pairs(plist) do
306 local prob = i.prob
307 if i.force_place == true then
308 prob = prob + 128
311 probability_list[#probability_list + 1] = {
312 pos = minetest.string_to_pos(_),
313 prob = prob,
317 local slist = minetest.deserialize(meta.slices)
318 local slice_list = {}
319 for _, i in pairs(slist) do
320 slice_list[#slice_list + 1] = {
321 ypos = pos.y + i.ypos,
322 prob = i.prob,
326 local filepath = path..meta.schem_name..".mts"
327 local res = minetest.create_schematic(pos1, pos2, probability_list, filepath, slice_list)
329 if res then
330 minetest.chat_send_player(name, minetest.colorize("#00ff00",
331 "Exported schematic to "..filepath))
332 else
333 minetest.chat_send_player(name, minetest.colorize("red",
334 "Failed to export schematic to "..filepath))
338 -- Save meta before updating visuals
339 local inv = realmeta:get_inventory():get_lists()
340 realmeta:from_table({fields = meta, inventory = inv})
342 -- Update border
343 if not fields.border and meta.schem_border == "true" then
344 advschem.mark(pos)
347 -- Update formspec
348 if not fields.quit then
349 advschem.show_formspec(pos, minetest.get_player_by_name(name), "main")
351 end,
354 advschem.add_form("slice", {
355 caption = "Y Slices",
356 tab = true,
357 get = function(self, pos, name, visible_panel)
358 local meta = minetest.get_meta(pos):to_table().fields
360 self.selected = self.selected or 1
361 local selected = tostring(self.selected)
362 local slice_list = minetest.deserialize(meta.slices)
363 local slices = ""
364 for _, i in pairs(slice_list) do
365 local insert = "Y = "..tostring(i.ypos).."; Probability = "..tostring(i.prob)
366 slices = slices..minetest.formspec_escape(insert)..","
368 slices = slices:sub(1, -2) -- Remove final comma
370 local form = [[
371 size[7,8]
372 table[0,0;6.8,6;slices;]]..slices..[[;]]..selected..[[]
375 if self.panel_add or self.panel_edit then
376 local ypos_default, prob_default = "", ""
377 local done_button = "button[5,7.18;2,1;done_add;Done]"
378 if self.panel_edit then
379 done_button = "button[5,7.18;2,1;done_edit;Done]"
380 ypos_default = slice_list[self.selected].ypos
381 prob_default = slice_list[self.selected].prob
384 form = form..[[
385 field[0.3,7.5;2.5,1;ypos;Y position (max. ]]..(meta.y_size - 1)..[[):;]]..ypos_default..[[]
386 field[2.8,7.5;2.5,1;prob;Probability (0-127):;]]..prob_default..[[]
387 field_close_on_enter[ypos;false]
388 field_close_on_enter[prob;false]
389 ]]..done_button
392 if not self.panel_edit then
393 form = form.."button[0,6;2,1;add;+ Add slice]"
396 if slices ~= "" and self.selected and not self.panel_add then
397 if not self.panel_edit then
398 form = form..[[
399 button[2,6;2,1;remove;- Remove slice]
400 button[4,6;2,1;edit;+/- Edit slice]
402 else
403 form = form..[[
404 button[2,6;2,1;remove;- Remove slice]
405 button[4,6;2,1;edit;+/- Edit slice]
410 return form
411 end,
412 handle = function(self, pos, name, fields)
413 local meta = minetest.get_meta(pos)
414 local player = minetest.get_player_by_name(name)
416 if fields.slices then
417 local slices = fields.slices:split(":")
418 self.selected = tonumber(slices[2])
421 if fields.add then
422 if not self.panel_add then
423 self.panel_add = true
424 advschem.show_formspec(pos, player, "slice")
425 else
426 self.panel_add = nil
427 advschem.show_formspec(pos, player, "slice")
431 local ypos, prob = tonumber(fields.ypos), tonumber(fields.prob)
432 if (fields.done_add or fields.done_edit) and fields.ypos and fields.prob and
433 fields.ypos ~= "" and fields.prob ~= "" and ypos and prob and
434 ypos <= (meta:get_int("y_size") - 1) and prob >= 0 and prob <= 255 then
435 local slice_list = minetest.deserialize(meta:get_string("slices"))
436 local index = #slice_list + 1
437 if fields.done_edit then
438 index = self.selected
441 slice_list[index] = {ypos = ypos, prob = prob}
443 meta:set_string("slices", minetest.serialize(slice_list))
445 -- Update and show formspec
446 self.panel_add = nil
447 advschem.show_formspec(pos, player, "slice")
450 if fields.remove and self.selected then
451 local slice_list = minetest.deserialize(meta:get_string("slices"))
452 slice_list[self.selected] = nil
453 meta:set_string("slices", minetest.serialize(renumber(slice_list)))
455 -- Update formspec
456 self.selected = 1
457 self.panel_edit = nil
458 advschem.show_formspec(pos, player, "slice")
461 if fields.edit then
462 if not self.panel_edit then
463 self.panel_edit = true
464 advschem.show_formspec(pos, player, "slice")
465 else
466 self.panel_edit = nil
467 advschem.show_formspec(pos, player, "slice")
470 end,
473 advschem.add_form("probtool", {
474 cache_name = false,
475 caption = "Schematic Node Probability Tool",
476 get = function(self, pos, name)
477 local player = minetest.get_player_by_name(name)
478 if not player then
479 return
481 local probtool = player:get_wielded_item()
482 if probtool:get_name() ~= "advschem:probtool" then
483 return
486 local meta = probtool:get_meta()
487 local prob = tonumber(meta:get_string("advschem_prob"))
488 local force_place = meta:get_string("advschem_force_place")
490 if not prob then
491 prob = 127
493 if force_place == nil or force_place == "" then
494 force_place = "false"
496 local form = "size[5,4]"..
497 "label[0,0;Schematic Node Probability Tool]"..
498 "field[0.75,1;4,1;prob;Probability (0-127);"..prob.."]"..
499 "checkbox[0.60,1.5;force_place;Force placement;" .. force_place .. "]" ..
500 "button_exit[0.25,3;2,1;cancel;Cancel]"..
501 "button_exit[2.75,3;2,1;submit;Apply]"..
502 "tooltip[prob;Probability that the node will be placed]"..
503 "tooltip[force_place;If enabled, the node will replace nodes other than air and ignore]"..
504 "field_close_on_enter[prob;false]"
505 return form
506 end,
507 handle = function(self, pos, name, fields)
508 if fields.submit then
509 local prob = tonumber(fields.prob)
510 if prob then
511 local player = minetest.get_player_by_name(name)
512 if not player then
513 return
515 local probtool = player:get_wielded_item()
516 if probtool:get_name() ~= "advschem:probtool" then
517 return
520 local force_place = self.force_place == true
522 set_item_metadata(probtool, prob, force_place)
524 player:set_wielded_item(probtool)
527 if fields.force_place == "true" then
528 self.force_place = true
529 elseif fields.force_place == "false" then
530 self.force_place = false
532 end,
536 --- API
539 --- Copies and modifies positions `pos1` and `pos2` so that each component of
540 -- `pos1` is less than or equal to the corresponding component of `pos2`.
541 -- Returns the new positions.
542 function advschem.sort_pos(pos1, pos2)
543 if not pos1 or not pos2 then
544 return
547 pos1, pos2 = table.copy(pos1), table.copy(pos2)
548 if pos1.x > pos2.x then
549 pos2.x, pos1.x = pos1.x, pos2.x
551 if pos1.y > pos2.y then
552 pos2.y, pos1.y = pos1.y, pos2.y
554 if pos1.z > pos2.z then
555 pos2.z, pos1.z = pos1.z, pos2.z
557 return pos1, pos2
560 -- [function] Prepare size
561 function advschem.size(pos)
562 local pos1 = vector.new(pos)
563 local meta = minetest.get_meta(pos)
564 local node = minetest.get_node(pos)
565 local param2 = node.param2
566 local size = {
567 x = meta:get_int("x_size"),
568 y = math.max(meta:get_int("y_size") - 1, 0),
569 z = meta:get_int("z_size"),
572 if param2 == 1 then
573 local new_pos = vector.add({x = size.z, y = size.y, z = -size.x}, pos)
574 pos1.x = pos1.x + 1
575 new_pos.z = new_pos.z + 1
576 return pos1, new_pos
577 elseif param2 == 2 then
578 local new_pos = vector.add({x = -size.x, y = size.y, z = -size.z}, pos)
579 pos1.z = pos1.z - 1
580 new_pos.x = new_pos.x + 1
581 return pos1, new_pos
582 elseif param2 == 3 then
583 local new_pos = vector.add({x = -size.z, y = size.y, z = size.x}, pos)
584 pos1.x = pos1.x - 1
585 new_pos.z = new_pos.z - 1
586 return pos1, new_pos
587 else
588 local new_pos = vector.add(size, pos)
589 pos1.z = pos1.z + 1
590 new_pos.x = new_pos.x - 1
591 return pos1, new_pos
595 -- [function] Mark region
596 function advschem.mark(pos)
597 advschem.unmark(pos)
599 local id = minetest.pos_to_string(pos)
600 local owner = minetest.get_meta(pos):get_string("owner")
601 local pos1, pos2 = advschem.size(pos)
602 pos1, pos2 = advschem.sort_pos(pos1, pos2)
604 local thickness = 0.2
605 local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
606 local m = {}
607 local low = true
608 local offset
610 -- XY plane markers
611 for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
612 if low then
613 offset = -0.01
614 else
615 offset = 0.01
617 local marker = minetest.add_entity({x = pos1.x + sizex - 0.5, y = pos1.y + sizey - 0.5, z = z + offset}, "advschem:display")
618 if marker ~= nil then
619 marker:set_properties({
620 visual_size={x=(sizex+0.01) * 2, y=sizey * 2},
622 marker:get_luaentity().id = id
623 marker:get_luaentity().owner = owner
624 table.insert(m, marker)
626 low = false
629 low = true
630 -- YZ plane markers
631 for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
632 if low then
633 offset = -0.01
634 else
635 offset = 0.01
638 local marker = minetest.add_entity({x = x + offset, y = pos1.y + sizey - 0.5, z = pos1.z + sizez - 0.5}, "advschem:display")
639 if marker ~= nil then
640 marker:set_properties({
641 visual_size={x=(sizez+0.01) * 2, y=sizey * 2},
643 marker:set_yaw(math.pi / 2)
644 marker:get_luaentity().id = id
645 marker:get_luaentity().owner = owner
646 table.insert(m, marker)
648 low = false
651 advschem.markers[id] = m
652 return true
655 -- [function] Unmark region
656 function advschem.unmark(pos)
657 local id = minetest.pos_to_string(pos)
658 if advschem.markers[id] then
659 local retval
660 for _, entity in ipairs(advschem.markers[id]) do
661 entity:remove()
662 retval = true
664 return retval
669 --- Mark node probability values near player
672 -- Show probability and force_place status of a particular position for player in HUD.
673 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
674 -- The distance to the node is also displayed below that. This can't be avoided and is
675 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
676 function advschem.display_node_prob(player, pos, prob, force_place)
677 local wpstring
678 if prob and force_place == true then
679 wpstring = string.format("%d [F]", prob)
680 elseif prob then
681 wpstring = prob
682 elseif force_place == true then
683 wpstring = "[F]"
685 if wpstring then
686 return player:hud_add({
687 hud_elem_type = "waypoint",
688 name = wpstring,
689 text = "m", -- For the distance artifact
690 number = text_color_number,
691 world_pos = pos,
696 -- Display the node probabilities and force_place status of the nodes near the player.
697 function advschem.display_node_probs_around_player(player)
698 local playername = player:get_player_name()
699 local pos = vector.round(player:getpos())
700 local dist = 5
701 for x=pos.x-dist, pos.x+dist do
702 for y=pos.y-dist, pos.y+dist do
703 for z=pos.z-dist, pos.z+dist do
704 local checkpos = {x=x, y=y, z=z}
705 local nodehash = minetest.hash_node_position(checkpos)
707 -- If node is already displayed, remove it so it can re replaced later
708 if displayed_waypoints[playername][nodehash] then
709 player:hud_remove(displayed_waypoints[playername][nodehash])
710 displayed_waypoints[playername][nodehash] = nil
713 local prob, force_place
714 local meta = minetest.get_meta(checkpos)
715 prob = tonumber(meta:get_string("advschem_prob"))
716 force_place = meta:get_string("advschem_force_place") == "true"
717 local hud_id = advschem.display_node_prob(player, checkpos, prob, force_place)
718 if hud_id then
719 displayed_waypoints[playername][nodehash] = hud_id
726 -- Remove all active displayed node statuses.
727 function advschem.clear_displayed_node_probs(player)
728 for nodehash, hud_id in pairs(displayed_waypoints[player:get_player_name()]) do
729 player:hud_remove(hud_id)
730 displayed_waypoints[player:get_player_name()][nodehash] = nil
734 minetest.register_on_joinplayer(function(player)
735 displayed_waypoints[player:get_player_name()] = {}
736 end)
738 minetest.register_on_leaveplayer(function(player)
739 displayed_waypoints[player:get_player_name()] = nil
740 end)
743 --- Registrations
746 -- [priv] schematic_override
747 minetest.register_privilege("schematic_override", {
748 description = "Allows you to access advschem nodes not owned by you",
749 give_to_singleplayer = false,
752 -- [node] Schematic creator
753 minetest.register_node("advschem:creator", {
754 description = "Schematic Creator",
755 _doc_items_longdesc = "The schematic creator is used to save a region of the world into a schematic file (.mts).",
756 _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"..
757 "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. You can use this name in the /placeschem command.".."\n"..
758 "The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.".."\n"..
759 "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 127 (127 is for 100%). By default, all Y slices occour always.".."\n"..
760 "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.",
761 tiles = {"advschem_creator_top.png", "advschem_creator_bottom.png",
762 "advschem_creator_sides.png"},
763 groups = { dig_immediate = 2},
764 paramtype2 = "facedir",
765 is_ground_content = false,
767 after_place_node = function(pos, player)
768 local name = player:get_player_name()
769 local meta = minetest.get_meta(pos)
771 meta:set_string("owner", name)
772 meta:set_string("infotext", "Schematic Creator\n(owned by "..name..")")
773 meta:set_string("prob_list", minetest.serialize({}))
774 meta:set_string("slices", minetest.serialize({}))
776 local node = minetest.get_node(pos)
777 local dir = minetest.facedir_to_dir(node.param2)
779 meta:set_int("x_size", 1)
780 meta:set_int("y_size", 1)
781 meta:set_int("z_size", 1)
783 -- Don't take item from itemstack
784 return true
785 end,
786 can_dig = function(pos, player)
787 local name = player:get_player_name()
788 local meta = minetest.get_meta(pos)
789 if meta:get_string("owner") == name or
790 minetest.check_player_privs(player, "schematic_override") == true then
791 return true
794 return false
795 end,
796 on_rightclick = function(pos, node, player)
797 local meta = minetest.get_meta(pos)
798 local name = player:get_player_name()
799 if meta:get_string("owner") == name or
800 minetest.check_player_privs(player, "schematic_override") == true then
801 -- Get player attribute
802 local tab = player:get_attribute("advschem:tab")
803 if not forms[tab] or not tab then
804 tab = "main"
807 advschem.show_formspec(pos, player, tab, true)
809 end,
810 after_destruct = function(pos)
811 advschem.unmark(pos)
812 end,
815 minetest.register_tool("advschem:probtool", {
816 description = "Schematic Node Probability Tool",
817 _doc_items_longdesc =
818 "This tool can be used together with a schematic creator to finetune the way how nodes from a schematic are placed.".."\n"..
819 "It allows you to do two things:".."\n"..
820 "1) Set a chance for a particular node not to be placed in schematic".."\n"..
821 "2) Enable a node to replace blocks other than air and ignored when placed in a schematic",
822 _doc_items_usagehelp = "Leftclick to select a probability (0-127; 127 is for 100%) and to enable or disable force placement. Now rightclick any node with this tool to apply these settings to the node. This information is preserved in the node until it is destroyed or the tool is used again. 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"..
823 "Note that this tool only has an effect on the nodes with regards to schematics. The node behaviour itself is not changed at all.",
824 wield_image = "advschem_probtool.png",
825 inventory_image = "advschem_probtool.png",
826 on_use = function(itemstack, user, pointed_thing)
827 -- Open dialog to change the probability to apply to nodes.
828 advschem.show_formspec(user:getpos(), user, "probtool", true)
829 end,
830 on_secondary_use = function(itemstack, user, pointed_thing)
831 advschem.clear_displayed_node_probs(user)
832 end,
833 on_place = function(itemstack, placer, pointed_thing)
835 -- This sets the node probability of pointed node to the
836 -- currently used probability stored in the tool.
838 local pos = pointed_thing.under
839 local node = minetest.get_node(pos)
840 -- Schematic void are ignored, they always have probability 0
841 if node.name == "advschem:void" then
842 return itemstack
844 local nmeta = minetest.get_meta(pos)
845 local imeta = itemstack:get_meta()
846 local prob = tonumber(imeta:get_string("advschem_prob"))
847 local force_place = imeta:get_string("advschem_force_place")
849 if not prob or prob == 127 then
850 nmeta:set_string("advschem_prob", nil)
851 else
852 nmeta:set_string("advschem_prob", prob)
854 if force_place == "true" then
855 nmeta:set_string("advschem_force_place", "true")
856 else
857 nmeta:set_string("advschem_force_place", nil)
860 -- Enable node probablity display
861 advschem.display_node_probs_around_player(placer)
863 return itemstack
864 end,
867 minetest.register_node("advschem:void", {
868 description = "Schematic Void",
869 _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.",
870 _doc_items_usagehelp = "Just place the schematic void like any other block and use the schematic creator to save a portion of the world.",
871 tiles = { "advschem_void.png" },
872 drawtype = "nodebox",
873 is_ground_content = false,
874 paramtype = "light",
875 walkable = false,
876 sunlight_propagates = true,
877 node_box = {
878 type = "fixed",
879 fixed = {
880 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
883 groups = { dig_immediate = 3},
886 -- [entity] Visible schematic border
887 minetest.register_entity("advschem:display", {
888 visual = "upright_sprite",
889 textures = {"advschem_border.png"},
890 visual_size = {x=10, y=10},
891 collisionbox = {0,0,0,0,0,0},
892 physical = false,
894 on_step = function(self, dtime)
895 if not self.id then
896 self.object:remove()
897 elseif not advschem.markers[self.id] then
898 self.object:remove()
900 end,
901 on_activate = function(self)
902 self.object:set_armor_groups({immortal = 1})
903 end,
906 -- [chatcommand] Place schematic
907 minetest.register_chatcommand("placeschem", {
908 description = "Place schematic at the position specified or the current "..
909 "player position (loaded from "..export_path_full..".",
910 privs = {debug = true},
911 params = "<schematic name>[.mts] [<x> <y> <z>]",
912 func = function(name, param)
913 local schem, p = string.match(param, "^([^ ]+) *(.*)$")
914 local pos = minetest.string_to_pos(p)
916 if not schem then
917 return false, "No schematic file specified."
920 if not pos then
921 pos = minetest.get_player_by_name(name):get_pos()
924 -- Automatiically add file name suffix if omitted
925 local schem_full
926 if string.sub(schem, string.len(schem)-3, string.len(schem)) == ".mts" then
927 schem_full = schem
928 else
929 schem_full = schem .. ".mts"
932 local success = minetest.place_schematic(pos, export_path_full .. DIR_DELIM .. schem_full, "random", nil, false)
934 if success == nil then
935 return false, "Schematic file could not be loaded!"
936 else
937 return true
939 end,