Update the probtool help
[minetest_schemedit.git] / init.lua
blob16219d45e17aa851378c7d4e6aed2d18b5247ada
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 hashpos = minetest.hash_node_position(scanpos)
149 prob_list[hashpos] = {
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)
223 local hashpos = minetest.hash_node_position(pos)
225 local border_button
226 if meta.schem_border == "true" and advschem.markers[hashpos] then
227 border_button = "button[3.5,7.5;3,1;border;Hide border]"
228 else
229 border_button = "button[3.5,7.5;3,1;border;Show border]"
232 -- TODO: Show information regarding volume, pos1, pos2, etc... in formspec
233 return [[
234 size[7,8]
235 label[0.5,-0.1;Position: ]]..strpos..[[]
236 label[3,-0.1;Owner: ]]..name..[[]
238 field[0.8,1;5,1;name;Schematic name:;]]..minetest.formspec_escape(meta.schem_name or "")..[[]
239 button[5.3,0.69;1.2,1;save_name;Save]
240 tooltip[save_name;Save schematic name]
241 field_close_on_enter[name;false]
243 button[0.5,1.5;6,1;export;Export schematic]
244 textarea[0.8,2.5;6.2,5;;The schematic will be exported as a .mts file and stored in]]..
245 "\n" .. export_path_full .. DIR_DELIM .. [[<name>.mts.;]
246 field[0.8,7;2,1;x;X size:;]]..meta.x_size..[[]
247 field[2.8,7;2,1;y;Y size:;]]..meta.y_size..[[]
248 field[4.8,7;2,1;z;Z size:;]]..meta.z_size..[[]
249 field_close_on_enter[x;false]
250 field_close_on_enter[y;false]
251 field_close_on_enter[z;false]
253 button[0.5,7.5;3,1;save;Save size]
254 ]]..
255 border_button
256 end,
257 handle = function(self, pos, name, fields)
258 local realmeta = minetest.get_meta(pos)
259 local meta = realmeta:to_table().fields
260 local hashpos = minetest.hash_node_position(pos)
262 -- Toggle border
263 if fields.border then
264 if meta.schem_border == "true" and advschem.markers[hashpos] then
265 advschem.unmark(pos)
266 meta.schem_border = "false"
267 else
268 advschem.mark(pos)
269 meta.schem_border = "true"
273 -- Save size vector values
274 if (fields.save or fields.key_enter_field == "x" or
275 fields.key_enter_field == "y" or fields.key_enter_field == "z")
276 and (fields.x and fields.y and fields.z and fields.x ~= ""
277 and fields.y ~= "" and fields.z ~= "") then
278 local x, y, z = tonumber(fields.x), tonumber(fields.y), tonumber(fields.z)
280 if x then
281 meta.x_size = math.max(x, 1)
283 if y then
284 meta.y_size = math.max(y, 1)
286 if z then
287 meta.z_size = math.max(z, 1)
291 -- Save schematic name
292 if fields.save_name or fields.key_enter_field == "name" and fields.name and
293 fields.name ~= "" then
294 meta.schem_name = fields.name
297 -- Export schematic
298 if fields.export and meta.schem_name and meta.schem_name ~= "" then
299 local pos1, pos2 = advschem.size(pos)
300 pos1, pos2 = advschem.sort_pos(pos1, pos2)
301 local path = export_path_full .. DIR_DELIM
302 minetest.mkdir(path)
304 local plist = advschem.scan_metadata(pos1, pos2)
305 local probability_list = {}
306 for hash, i in pairs(plist) do
307 local prob = i.prob
308 if i.force_place == true then
309 prob = prob + 128
312 table.insert(probability_list, {
313 pos = minetest.get_position_from_hash(hash),
314 prob = prob,
318 local slist = minetest.deserialize(meta.slices)
319 local slice_list = {}
320 for _, i in pairs(slist) do
321 slice_list[#slice_list + 1] = {
322 ypos = pos.y + i.ypos,
323 prob = i.prob,
327 local filepath = path..meta.schem_name..".mts"
328 local res = minetest.create_schematic(pos1, pos2, probability_list, filepath, slice_list)
330 if res then
331 minetest.chat_send_player(name, minetest.colorize("#00ff00",
332 "Exported schematic to "..filepath))
333 else
334 minetest.chat_send_player(name, minetest.colorize("red",
335 "Failed to export schematic to "..filepath))
339 -- Save meta before updating visuals
340 local inv = realmeta:get_inventory():get_lists()
341 realmeta:from_table({fields = meta, inventory = inv})
343 -- Update border
344 if not fields.border and meta.schem_border == "true" then
345 advschem.mark(pos)
348 -- Update formspec
349 if not fields.quit then
350 advschem.show_formspec(pos, minetest.get_player_by_name(name), "main")
352 end,
355 advschem.add_form("slice", {
356 caption = "Y Slices",
357 tab = true,
358 get = function(self, pos, name, visible_panel)
359 local meta = minetest.get_meta(pos):to_table().fields
361 self.selected = self.selected or 1
362 local selected = tostring(self.selected)
363 local slice_list = minetest.deserialize(meta.slices)
364 local slices = ""
365 for _, i in pairs(slice_list) do
366 local insert = "Y = "..tostring(i.ypos).."; Probability = "..tostring(i.prob)
367 slices = slices..minetest.formspec_escape(insert)..","
369 slices = slices:sub(1, -2) -- Remove final comma
371 local form = [[
372 size[7,8]
373 table[0,0;6.8,6;slices;]]..slices..[[;]]..selected..[[]
376 if self.panel_add or self.panel_edit then
377 local ypos_default, prob_default = "", ""
378 local done_button = "button[5,7.18;2,1;done_add;Done]"
379 if self.panel_edit then
380 done_button = "button[5,7.18;2,1;done_edit;Done]"
381 ypos_default = slice_list[self.selected].ypos
382 prob_default = slice_list[self.selected].prob
385 form = form..[[
386 field[0.3,7.5;2.5,1;ypos;Y position (max. ]]..(meta.y_size - 1)..[[):;]]..ypos_default..[[]
387 field[2.8,7.5;2.5,1;prob;Probability (0-127):;]]..prob_default..[[]
388 field_close_on_enter[ypos;false]
389 field_close_on_enter[prob;false]
390 ]]..done_button
393 if not self.panel_edit then
394 form = form.."button[0,6;2,1;add;+ Add slice]"
397 if slices ~= "" and self.selected and not self.panel_add then
398 if not self.panel_edit then
399 form = form..[[
400 button[2,6;2,1;remove;- Remove slice]
401 button[4,6;2,1;edit;+/- Edit slice]
403 else
404 form = form..[[
405 button[2,6;2,1;remove;- Remove slice]
406 button[4,6;2,1;edit;+/- Edit slice]
411 return form
412 end,
413 handle = function(self, pos, name, fields)
414 local meta = minetest.get_meta(pos)
415 local player = minetest.get_player_by_name(name)
417 if fields.slices then
418 local slices = fields.slices:split(":")
419 self.selected = tonumber(slices[2])
422 if fields.add then
423 if not self.panel_add then
424 self.panel_add = true
425 advschem.show_formspec(pos, player, "slice")
426 else
427 self.panel_add = nil
428 advschem.show_formspec(pos, player, "slice")
432 local ypos, prob = tonumber(fields.ypos), tonumber(fields.prob)
433 if (fields.done_add or fields.done_edit) and fields.ypos and fields.prob and
434 fields.ypos ~= "" and fields.prob ~= "" and ypos and prob and
435 ypos <= (meta:get_int("y_size") - 1) and prob >= 0 and prob <= 255 then
436 local slice_list = minetest.deserialize(meta:get_string("slices"))
437 local index = #slice_list + 1
438 if fields.done_edit then
439 index = self.selected
442 slice_list[index] = {ypos = ypos, prob = prob}
444 meta:set_string("slices", minetest.serialize(slice_list))
446 -- Update and show formspec
447 self.panel_add = nil
448 advschem.show_formspec(pos, player, "slice")
451 if fields.remove and self.selected then
452 local slice_list = minetest.deserialize(meta:get_string("slices"))
453 slice_list[self.selected] = nil
454 meta:set_string("slices", minetest.serialize(renumber(slice_list)))
456 -- Update formspec
457 self.selected = 1
458 self.panel_edit = nil
459 advschem.show_formspec(pos, player, "slice")
462 if fields.edit then
463 if not self.panel_edit then
464 self.panel_edit = true
465 advschem.show_formspec(pos, player, "slice")
466 else
467 self.panel_edit = nil
468 advschem.show_formspec(pos, player, "slice")
471 end,
474 advschem.add_form("probtool", {
475 cache_name = false,
476 caption = "Schematic Node Probability Tool",
477 get = function(self, pos, name)
478 local player = minetest.get_player_by_name(name)
479 if not player then
480 return
482 local probtool = player:get_wielded_item()
483 if probtool:get_name() ~= "advschem:probtool" then
484 return
487 local meta = probtool:get_meta()
488 local prob = tonumber(meta:get_string("advschem_prob"))
489 local force_place = meta:get_string("advschem_force_place")
491 if not prob then
492 prob = 127
494 if force_place == nil or force_place == "" then
495 force_place = "false"
497 local form = "size[5,4]"..
498 "label[0,0;Schematic Node Probability Tool]"..
499 "field[0.75,1;4,1;prob;Probability (0-127);"..prob.."]"..
500 "checkbox[0.60,1.5;force_place;Force placement;" .. force_place .. "]" ..
501 "button_exit[0.25,3;2,1;cancel;Cancel]"..
502 "button_exit[2.75,3;2,1;submit;Apply]"..
503 "tooltip[prob;Probability that the node will be placed]"..
504 "tooltip[force_place;If enabled, the node will replace nodes other than air and ignore]"..
505 "field_close_on_enter[prob;false]"
506 return form
507 end,
508 handle = function(self, pos, name, fields)
509 if fields.submit then
510 local prob = tonumber(fields.prob)
511 if prob then
512 local player = minetest.get_player_by_name(name)
513 if not player then
514 return
516 local probtool = player:get_wielded_item()
517 if probtool:get_name() ~= "advschem:probtool" then
518 return
521 local force_place = self.force_place == true
523 set_item_metadata(probtool, prob, force_place)
525 player:set_wielded_item(probtool)
528 if fields.force_place == "true" then
529 self.force_place = true
530 elseif fields.force_place == "false" then
531 self.force_place = false
533 end,
537 --- API
540 --- Copies and modifies positions `pos1` and `pos2` so that each component of
541 -- `pos1` is less than or equal to the corresponding component of `pos2`.
542 -- Returns the new positions.
543 function advschem.sort_pos(pos1, pos2)
544 if not pos1 or not pos2 then
545 return
548 pos1, pos2 = table.copy(pos1), table.copy(pos2)
549 if pos1.x > pos2.x then
550 pos2.x, pos1.x = pos1.x, pos2.x
552 if pos1.y > pos2.y then
553 pos2.y, pos1.y = pos1.y, pos2.y
555 if pos1.z > pos2.z then
556 pos2.z, pos1.z = pos1.z, pos2.z
558 return pos1, pos2
561 -- [function] Prepare size
562 function advschem.size(pos)
563 local pos1 = vector.new(pos)
564 local meta = minetest.get_meta(pos)
565 local node = minetest.get_node(pos)
566 local param2 = node.param2
567 local size = {
568 x = meta:get_int("x_size"),
569 y = math.max(meta:get_int("y_size") - 1, 0),
570 z = meta:get_int("z_size"),
573 if param2 == 1 then
574 local new_pos = vector.add({x = size.z, y = size.y, z = -size.x}, pos)
575 pos1.x = pos1.x + 1
576 new_pos.z = new_pos.z + 1
577 return pos1, new_pos
578 elseif param2 == 2 then
579 local new_pos = vector.add({x = -size.x, y = size.y, z = -size.z}, pos)
580 pos1.z = pos1.z - 1
581 new_pos.x = new_pos.x + 1
582 return pos1, new_pos
583 elseif param2 == 3 then
584 local new_pos = vector.add({x = -size.z, y = size.y, z = size.x}, pos)
585 pos1.x = pos1.x - 1
586 new_pos.z = new_pos.z - 1
587 return pos1, new_pos
588 else
589 local new_pos = vector.add(size, pos)
590 pos1.z = pos1.z + 1
591 new_pos.x = new_pos.x - 1
592 return pos1, new_pos
596 -- [function] Mark region
597 function advschem.mark(pos)
598 advschem.unmark(pos)
600 local id = minetest.hash_node_position(pos)
601 local owner = minetest.get_meta(pos):get_string("owner")
602 local pos1, pos2 = advschem.size(pos)
603 pos1, pos2 = advschem.sort_pos(pos1, pos2)
605 local thickness = 0.2
606 local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
607 local m = {}
608 local low = true
609 local offset
611 -- XY plane markers
612 for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
613 if low then
614 offset = -0.01
615 else
616 offset = 0.01
618 local marker = minetest.add_entity({x = pos1.x + sizex - 0.5, y = pos1.y + sizey - 0.5, z = z + offset}, "advschem:display")
619 if marker ~= nil then
620 marker:set_properties({
621 visual_size={x=(sizex+0.01) * 2, y=sizey * 2},
623 marker:get_luaentity().id = id
624 marker:get_luaentity().owner = owner
625 table.insert(m, marker)
627 low = false
630 low = true
631 -- YZ plane markers
632 for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
633 if low then
634 offset = -0.01
635 else
636 offset = 0.01
639 local marker = minetest.add_entity({x = x + offset, y = pos1.y + sizey - 0.5, z = pos1.z + sizez - 0.5}, "advschem:display")
640 if marker ~= nil then
641 marker:set_properties({
642 visual_size={x=(sizez+0.01) * 2, y=sizey * 2},
644 marker:set_yaw(math.pi / 2)
645 marker:get_luaentity().id = id
646 marker:get_luaentity().owner = owner
647 table.insert(m, marker)
649 low = false
652 advschem.markers[id] = m
653 return true
656 -- [function] Unmark region
657 function advschem.unmark(pos)
658 local id = minetest.hash_node_position(pos)
659 if advschem.markers[id] then
660 local retval
661 for _, entity in ipairs(advschem.markers[id]) do
662 entity:remove()
663 retval = true
665 return retval
670 --- Mark node probability values near player
673 -- Show probability and force_place status of a particular position for player in HUD.
674 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
675 -- The distance to the node is also displayed below that. This can't be avoided and is
676 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
677 function advschem.display_node_prob(player, pos, prob, force_place)
678 local wpstring
679 if prob and force_place == true then
680 wpstring = string.format("%d [F]", prob)
681 elseif prob then
682 wpstring = prob
683 elseif force_place == true then
684 wpstring = "[F]"
686 if wpstring then
687 return player:hud_add({
688 hud_elem_type = "waypoint",
689 name = wpstring,
690 text = "m", -- For the distance artifact
691 number = text_color_number,
692 world_pos = pos,
697 -- Display the node probabilities and force_place status of the nodes in a region.
698 -- By default, this is done for nodes near the player (distance: 5).
699 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
700 function advschem.display_node_probs_region(player, pos1, pos2)
701 local playername = player:get_player_name()
702 local pos = vector.round(player:getpos())
704 local dist = 5
705 -- Default: 5 nodes away from player in any direction
706 if not pos1 then
707 pos1 = vector.subtract(pos, dist)
709 if not pos2 then
710 pos2 = vector.add(pos, dist)
712 for x=pos1.x, pos2.x do
713 for y=pos1.y, pos2.y do
714 for z=pos1.z, pos2.z do
715 local checkpos = {x=x, y=y, z=z}
716 local nodehash = minetest.hash_node_position(checkpos)
718 -- If node is already displayed, remove it so it can re replaced later
719 if displayed_waypoints[playername][nodehash] then
720 player:hud_remove(displayed_waypoints[playername][nodehash])
721 displayed_waypoints[playername][nodehash] = nil
724 local prob, force_place
725 local meta = minetest.get_meta(checkpos)
726 prob = tonumber(meta:get_string("advschem_prob"))
727 force_place = meta:get_string("advschem_force_place") == "true"
728 local hud_id = advschem.display_node_prob(player, checkpos, prob, force_place)
729 if hud_id then
730 displayed_waypoints[playername][nodehash] = hud_id
731 displayed_waypoints[playername].display_active = true
738 -- Remove all active displayed node statuses.
739 function advschem.clear_displayed_node_probs(player)
740 local playername = player:get_player_name()
741 for nodehash, hud_id in pairs(displayed_waypoints[playername]) do
742 player:hud_remove(hud_id)
743 displayed_waypoints[playername][nodehash] = nil
744 displayed_waypoints[playername].display_active = false
748 minetest.register_on_joinplayer(function(player)
749 displayed_waypoints[player:get_player_name()] = {
750 display_active = false -- If true, there *might* be at least one active node prob HUD display
751 -- If false, no node probabilities are displayed for sure.
753 end)
755 minetest.register_on_leaveplayer(function(player)
756 displayed_waypoints[player:get_player_name()] = nil
757 end)
759 -- Regularily clear the displayed node probabilities and force_place
760 -- for all players who do not wield the probtool.
761 -- This makes sure the screen is not spammed with information when it
762 -- isn't needed.
763 local cleartimer = 0
764 minetest.register_globalstep(function(dtime)
765 cleartimer = cleartimer + dtime
766 if cleartimer > 2 then
767 local players = minetest.get_connected_players()
768 for p = 1, #players do
769 local player = players[p]
770 local pname = player:get_player_name()
771 if displayed_waypoints[pname].display_active then
772 local item = player:get_wielded_item()
773 if item:get_name() ~= "advschem:probtool" then
774 advschem.clear_displayed_node_probs(player)
778 cleartimer = 0
780 end)
783 --- Registrations
786 -- [priv] schematic_override
787 minetest.register_privilege("schematic_override", {
788 description = "Allows you to access advschem nodes not owned by you",
789 give_to_singleplayer = false,
792 -- [node] Schematic creator
793 minetest.register_node("advschem:creator", {
794 description = "Schematic Creator",
795 _doc_items_longdesc = "The schematic creator is used to save a region of the world into a schematic file (.mts).",
796 _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"..
797 "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"..
798 "The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.".."\n"..
799 "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"..
800 "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.",
801 tiles = {"advschem_creator_top.png", "advschem_creator_bottom.png",
802 "advschem_creator_sides.png"},
803 groups = { dig_immediate = 2},
804 paramtype2 = "facedir",
805 is_ground_content = false,
807 after_place_node = function(pos, player)
808 local name = player:get_player_name()
809 local meta = minetest.get_meta(pos)
811 meta:set_string("owner", name)
812 meta:set_string("infotext", "Schematic Creator\n(owned by "..name..")")
813 meta:set_string("prob_list", minetest.serialize({}))
814 meta:set_string("slices", minetest.serialize({}))
816 local node = minetest.get_node(pos)
817 local dir = minetest.facedir_to_dir(node.param2)
819 meta:set_int("x_size", 1)
820 meta:set_int("y_size", 1)
821 meta:set_int("z_size", 1)
823 -- Don't take item from itemstack
824 return true
825 end,
826 can_dig = function(pos, player)
827 local name = player:get_player_name()
828 local meta = minetest.get_meta(pos)
829 if meta:get_string("owner") == name or
830 minetest.check_player_privs(player, "schematic_override") == true then
831 return true
834 return false
835 end,
836 on_rightclick = function(pos, node, player)
837 local meta = minetest.get_meta(pos)
838 local name = player:get_player_name()
839 if meta:get_string("owner") == name or
840 minetest.check_player_privs(player, "schematic_override") == true then
841 -- Get player attribute
842 local tab = player:get_attribute("advschem:tab")
843 if not forms[tab] or not tab then
844 tab = "main"
847 advschem.show_formspec(pos, player, tab, true)
849 end,
850 after_destruct = function(pos)
851 advschem.unmark(pos)
852 end,
855 minetest.register_tool("advschem:probtool", {
856 description = "Schematic Node Probability Tool",
857 _doc_items_longdesc =
858 "This tool can be used together with a schematic creator to finetune the way how nodes from a schematic are placed.".."\n"..
859 "It allows you to do two things:".."\n"..
860 "1) Set a chance for a particular node not to be placed in schematic".."\n"..
861 "2) Enable a node to replace blocks other than air and ignored when placed in a schematic",
862 _doc_items_usagehelp = "\n"..
863 "BASIC USAGE:".."\n"..
864 "Punch to set the values to apply to nodes. Select a probability (0-127; 127 is for 100%) and to enable or disable force placement (force-placed nodes will overwrite any node). Now place the tool on any to apply these settings to the node. This information is preserved in the node until it is destroyed or changed by the tool again.".."\n"..
865 "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"..
866 "NODE HUD:".."\n"..
867 "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"..
868 "To disable the node HUD, unselect the tool or hit “place” while not pointing anything.".."\n\n"..
869 "UPDATING THE NODE HUD:".."\n"..
870 "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.",
871 wield_image = "advschem_probtool.png",
872 inventory_image = "advschem_probtool.png",
873 liquids_pointable = true,
874 on_use = function(itemstack, user, pointed_thing)
875 local ctrl = user:get_player_control()
876 -- Simple use
877 if not ctrl.sneak then
878 -- Open dialog to change the probability to apply to nodes
879 advschem.show_formspec(user:getpos(), user, "probtool", true)
881 -- Use + sneak
882 else
883 -- Display the probability and force_place values for nodes.
885 -- If a schematic creator was punched, only enable display for all nodes
886 -- within the creator's region.
887 local use_creator_region = false
888 if pointed_thing and pointed_thing.type == "node" and pointed_thing.under then
889 punchpos = pointed_thing.under
890 local node = minetest.get_node(punchpos)
891 if node.name == "advschem:creator" then
892 local pos1, pos2 = advschem.size(punchpos)
893 pos1, pos2 = advschem.sort_pos(pos1, pos2)
894 advschem.display_node_probs_region(user, pos1, pos2)
895 return
899 -- Otherwise, just display the region close to the player
900 advschem.display_node_probs_region(user)
902 end,
903 on_secondary_use = function(itemstack, user, pointed_thing)
904 advschem.clear_displayed_node_probs(user)
905 end,
906 -- Set note probability and force_place and enable node probability display
907 on_place = function(itemstack, placer, pointed_thing)
908 -- Use pointed node's on_rightclick function first, if present
909 local node = minetest.get_node(pointed_thing.under)
910 if placer and not placer:get_player_control().sneak then
911 if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
912 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
916 -- This sets the node probability of pointed node to the
917 -- currently used probability stored in the tool.
918 local pos = pointed_thing.under
919 local node = minetest.get_node(pos)
920 -- Schematic void are ignored, they always have probability 0
921 if node.name == "advschem:void" then
922 return itemstack
924 local nmeta = minetest.get_meta(pos)
925 local imeta = itemstack:get_meta()
926 local prob = tonumber(imeta:get_string("advschem_prob"))
927 local force_place = imeta:get_string("advschem_force_place")
929 if not prob or prob == 127 then
930 nmeta:set_string("advschem_prob", nil)
931 else
932 nmeta:set_string("advschem_prob", prob)
934 if force_place == "true" then
935 nmeta:set_string("advschem_force_place", "true")
936 else
937 nmeta:set_string("advschem_force_place", nil)
940 -- Enable node probablity display
941 advschem.display_node_probs_region(placer)
943 return itemstack
944 end,
947 minetest.register_node("advschem:void", {
948 description = "Schematic Void",
949 _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.",
950 _doc_items_usagehelp = "Just place the schematic void like any other block and use the schematic creator to save a portion of the world.",
951 tiles = { "advschem_void.png" },
952 drawtype = "nodebox",
953 is_ground_content = false,
954 paramtype = "light",
955 walkable = false,
956 sunlight_propagates = true,
957 node_box = {
958 type = "fixed",
959 fixed = {
960 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
963 groups = { dig_immediate = 3},
966 -- [entity] Visible schematic border
967 minetest.register_entity("advschem:display", {
968 visual = "upright_sprite",
969 textures = {"advschem_border.png"},
970 visual_size = {x=10, y=10},
971 collisionbox = {0,0,0,0,0,0},
972 physical = false,
974 on_step = function(self, dtime)
975 if not self.id then
976 self.object:remove()
977 elseif not advschem.markers[self.id] then
978 self.object:remove()
980 end,
981 on_activate = function(self)
982 self.object:set_armor_groups({immortal = 1})
983 end,
986 -- [chatcommand] Place schematic
987 minetest.register_chatcommand("placeschem", {
988 description = "Place schematic at the position specified or the current "..
989 "player position (loaded from "..export_path_full..".",
990 privs = {debug = true},
991 params = "<schematic name>[.mts] [<x> <y> <z>]",
992 func = function(name, param)
993 local schem, p = string.match(param, "^([^ ]+) *(.*)$")
994 local pos = minetest.string_to_pos(p)
996 if not schem then
997 return false, "No schematic file specified."
1000 if not pos then
1001 pos = minetest.get_player_by_name(name):get_pos()
1004 -- Automatiically add file name suffix if omitted
1005 local schem_full
1006 if string.sub(schem, string.len(schem)-3, string.len(schem)) == ".mts" then
1007 schem_full = schem
1008 else
1009 schem_full = schem .. ".mts"
1012 local success = minetest.place_schematic(pos, export_path_full .. DIR_DELIM .. schem_full, "random", nil, false)
1014 if success == nil then
1015 return false, "Schematic file could not be loaded!"
1016 else
1017 return true
1019 end,