Display probability in tool wear bar
[minetest_schemedit.git] / init.lua
blobba0c2e00628009ebec892a2e51ef8b6a744cda3e
1 local S = minetest.get_translator("schemedit")
2 local F = minetest.formspec_escape
4 local schemedit = {}
6 -- Directory delimeter fallback (normally comes from builtin)
7 if not DIR_DELIM then
8 DIR_DELIM = "/"
9 end
10 local export_path_full = table.concat({minetest.get_worldpath(), "schems"}, DIR_DELIM)
12 -- truncated export path so the server directory structure is not exposed publicly
13 local export_path_trunc = table.concat({S("<world path>"), "schems"}, DIR_DELIM)
15 local text_color = "#D79E9E"
16 local text_color_number = 0xD79E9E
18 schemedit.markers = {}
20 -- [local function] Renumber table
21 local function renumber(t)
22 local res = {}
23 for _, i in pairs(t) do
24 res[#res + 1] = i
25 end
26 return res
27 end
29 ---
30 --- Formspec API
31 ---
33 local contexts = {}
34 local form_data = {}
35 local tabs = {}
36 local forms = {}
37 local displayed_waypoints = {}
39 -- Sadly, the probabilities presented in Lua (0-255) are not identical to the REAL probabilities in the
40 -- schematic file (0-127). There are two converter functions to convert from one probability type to another.
41 -- This mod tries to retain the “Lua probability” as long as possible and only switches to “schematic probability”
42 -- on an actual export to a schematic.
44 function schemedit.lua_prob_to_schematic_prob(lua_prob)
45 return math.floor(lua_prob / 2)
46 end
48 function schemedit.schematic_prob_to_lua_prob(schematic_prob)
49 return schematic_prob * 2
51 end
53 -- [function] Add form
54 function schemedit.add_form(name, def)
55 def.name = name
56 forms[name] = def
58 if def.tab then
59 tabs[#tabs + 1] = name
60 end
61 end
63 -- [function] Generate tabs
64 function schemedit.generate_tabs(current)
65 local retval = "tabheader[0,0;tabs;"
66 for _, t in pairs(tabs) do
67 local f = forms[t]
68 if f.tab ~= false and f.caption then
69 retval = retval..f.caption..","
71 if type(current) ~= "number" and current == f.name then
72 current = _
73 end
74 end
75 end
76 retval = retval:sub(1, -2) -- Strip last comma
77 retval = retval..";"..current.."]" -- Close tabheader
78 return retval
79 end
81 -- [function] Handle tabs
82 function schemedit.handle_tabs(pos, name, fields)
83 local tab = tonumber(fields.tabs)
84 if tab and tabs[tab] and forms[tabs[tab]] then
85 schemedit.show_formspec(pos, name, forms[tabs[tab]].name)
86 return true
87 end
88 end
90 -- [function] Show formspec
91 function schemedit.show_formspec(pos, player, tab, show, ...)
92 if forms[tab] then
93 if type(player) == "string" then
94 player = minetest.get_player_by_name(player)
95 end
96 local name = player:get_player_name()
98 if show ~= false then
99 if not form_data[name] then
100 form_data[name] = {}
103 local form = forms[tab].get(form_data[name], pos, name, ...)
104 if forms[tab].tab then
105 form = form..schemedit.generate_tabs(tab)
108 minetest.show_formspec(name, "schemedit:"..tab, form)
109 contexts[name] = pos
111 -- Update player attribute
112 if forms[tab].cache_name ~= false then
113 player:set_attribute("schemedit:tab", tab)
115 else
116 minetest.close_formspec(pname, "schemedit:"..tab)
121 -- [event] On receive fields
122 minetest.register_on_player_receive_fields(function(player, formname, fields)
123 local formname = formname:split(":")
125 if formname[1] == "schemedit" and forms[formname[2]] then
126 local handle = forms[formname[2]].handle
127 local name = player:get_player_name()
128 if contexts[name] then
129 if not form_data[name] then
130 form_data[name] = {}
133 if not schemedit.handle_tabs(contexts[name], name, fields) and handle then
134 handle(form_data[name], contexts[name], name, fields)
138 end)
140 -- Helper function. Scans probabilities of all nodes in the given area and returns a prob_list
141 schemedit.scan_metadata = function(pos1, pos2)
142 local prob_list = {}
144 for x=pos1.x, pos2.x do
145 for y=pos1.y, pos2.y do
146 for z=pos1.z, pos2.z do
147 local scanpos = {x=x, y=y, z=z}
148 local node = minetest.get_node_or_nil(scanpos)
150 local prob, force_place
151 if node == nil or node.name == "schemedit:void" then
152 prob = 0
153 force_place = false
154 else
155 local meta = minetest.get_meta(scanpos)
157 prob = tonumber(meta:get_string("schemedit_prob")) or 255
158 local fp = meta:get_string("schemedit_force_place")
159 if fp == "true" then
160 force_place = true
161 else
162 force_place = false
166 local hashpos = minetest.hash_node_position(scanpos)
167 prob_list[hashpos] = {
168 pos = scanpos,
169 prob = prob,
170 force_place = force_place,
176 return prob_list
179 -- Sets probability and force_place metadata of an item.
180 -- Also updates item description.
181 -- The itemstack is updated in-place.
182 local function set_item_metadata(itemstack, prob, force_place)
183 local smeta = itemstack:get_meta()
184 local prob_desc = "\n"..S("Probability: @1", prob or
185 smeta:get_string("schemedit_prob") or S("Not Set"))
186 -- Update probability
187 if prob and prob >= 0 and prob < 255 then
188 smeta:set_string("schemedit_prob", tostring(prob))
189 elseif prob and prob == 255 then
190 -- Clear prob metadata for default probability
191 prob_desc = ""
192 smeta:set_string("schemedit_prob", nil)
193 else
194 prob_desc = "\n"..S("Probability: @1", smeta:get_string("schemedit_prob") or
195 S("Not Set"))
198 -- Update force place
199 if force_place == true then
200 smeta:set_string("schemedit_force_place", "true")
201 elseif force_place == false then
202 smeta:set_string("schemedit_force_place", nil)
205 -- Update description
206 local desc = minetest.registered_items[itemstack:get_name()].description
207 local meta_desc = smeta:get_string("description")
208 if meta_desc and meta_desc ~= "" then
209 desc = meta_desc
212 local original_desc = smeta:get_string("original_description")
213 if original_desc and original_desc ~= "" then
214 desc = original_desc
215 else
216 smeta:set_string("original_description", desc)
219 local force_desc = ""
220 if smeta:get_string("schemedit_force_place") == "true" then
221 force_desc = "\n"..S("Force placement")
224 desc = desc..minetest.colorize(text_color, prob_desc..force_desc)
226 smeta:set_string("description", desc)
228 return itemstack
232 --- Formspec Tabs
235 schemedit.add_form("main", {
236 tab = true,
237 caption = "Main",
238 get = function(self, pos, name)
239 local meta = minetest.get_meta(pos):to_table().fields
240 local strpos = minetest.pos_to_string(pos)
241 local hashpos = minetest.hash_node_position(pos)
243 local border_button
244 if meta.schem_border == "true" and schemedit.markers[hashpos] then
245 border_button = "button[3.5,7.5;3,1;border;"..F(S("Hide border")).."]"
246 else
247 border_button = "button[3.5,7.5;3,1;border;"..F(S("Show border")).."]"
250 -- TODO: Show information regarding volume, pos1, pos2, etc... in formspec
251 local form = [[
252 size[7,8]
253 label[0.5,-0.1;]]..F(S("Position: @1", strpos))..[[]
254 label[3,-0.1;]]..F(S("Owner: @1", name))..[[]
256 field[0.8,1;5,1;name;]]..F(S("Schematic name:"))..[[;]]..F(meta.schem_name or "")..[[]
257 button[5.3,0.69;1.2,1;save_name;]]..F(S("Save"))..[[]
258 tooltip[save_name;]]..F(S("Save schematic name"))..[[]
259 field_close_on_enter[name;false]
261 button[0.5,1.5;6,1;export;]]..F(S("Export schematic"))..[[]
262 textarea[0.8,2.5;6.2,5;;]]..F(S("The schematic will be exported as a .mts file and stored in\n@1",
263 export_path_trunc .. DIR_DELIM .. "<name>.mts."))..[[;]
264 field[0.8,7;2,1;x;X size:;]]..meta.x_size..[[]
265 field[2.8,7;2,1;y;Y size:;]]..meta.y_size..[[]
266 field[4.8,7;2,1;z;Z size:;]]..meta.z_size..[[]
267 field_close_on_enter[x;false]
268 field_close_on_enter[y;false]
269 field_close_on_enter[z;false]
271 button[0.5,7.5;3,1;save;]]..F(S("Save size"))..[[]
272 ]]..
273 border_button
274 if minetest.get_modpath("doc") then
275 form = form .. "image_button[6.4,-0.2;0.8,0.8;doc_button_icon_lores.png;doc;]" ..
276 "tooltip[doc;"..F(S("Help")).."]"
278 return form
279 end,
280 handle = function(self, pos, name, fields)
281 local realmeta = minetest.get_meta(pos)
282 local meta = realmeta:to_table().fields
283 local hashpos = minetest.hash_node_position(pos)
285 if fields.doc then
286 doc.show_entry(name, "nodes", "schemedit:creator", true)
287 return
290 -- Toggle border
291 if fields.border then
292 if meta.schem_border == "true" and schemedit.markers[hashpos] then
293 schemedit.unmark(pos)
294 meta.schem_border = "false"
295 else
296 schemedit.mark(pos)
297 meta.schem_border = "true"
301 -- Save size vector values
302 if (fields.save or fields.key_enter_field == "x" or
303 fields.key_enter_field == "y" or fields.key_enter_field == "z")
304 and (fields.x and fields.y and fields.z and fields.x ~= ""
305 and fields.y ~= "" and fields.z ~= "") then
306 local x, y, z = tonumber(fields.x), tonumber(fields.y), tonumber(fields.z)
308 if x then
309 meta.x_size = math.max(x, 1)
311 if y then
312 meta.y_size = math.max(y, 1)
314 if z then
315 meta.z_size = math.max(z, 1)
319 -- Save schematic name
320 if fields.save_name or fields.key_enter_field == "name" and fields.name and
321 fields.name ~= "" then
322 meta.schem_name = fields.name
325 -- Export schematic
326 if fields.export and meta.schem_name and meta.schem_name ~= "" then
327 local pos1, pos2 = schemedit.size(pos)
328 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
329 local path = export_path_full .. DIR_DELIM
330 minetest.mkdir(path)
332 local plist = schemedit.scan_metadata(pos1, pos2)
333 local probability_list = {}
334 for hash, i in pairs(plist) do
335 local prob = schemedit.lua_prob_to_schematic_prob(i.prob)
336 if i.force_place == true then
337 prob = prob + 128
340 table.insert(probability_list, {
341 pos = minetest.get_position_from_hash(hash),
342 prob = prob,
346 local slist = minetest.deserialize(meta.slices)
347 local slice_list = {}
348 for _, i in pairs(slist) do
349 slice_list[#slice_list + 1] = {
350 ypos = pos.y + i.ypos,
351 prob = schemedit.lua_prob_to_schematic_prob(i.prob),
355 local filepath = path..meta.schem_name..".mts"
356 local res = minetest.create_schematic(pos1, pos2, probability_list, filepath, slice_list)
358 if res then
359 minetest.chat_send_player(name, minetest.colorize("#00ff00",
360 S("Exported schematic to @1", filepath)))
361 else
362 minetest.chat_send_player(name, minetest.colorize("red",
363 S("Failed to export schematic to @1", filepath)))
367 -- Save meta before updating visuals
368 local inv = realmeta:get_inventory():get_lists()
369 realmeta:from_table({fields = meta, inventory = inv})
371 -- Update border
372 if not fields.border and meta.schem_border == "true" then
373 schemedit.mark(pos)
376 -- Update formspec
377 if not fields.quit then
378 schemedit.show_formspec(pos, minetest.get_player_by_name(name), "main")
380 end,
383 schemedit.add_form("slice", {
384 caption = S("Y Slices"),
385 tab = true,
386 get = function(self, pos, name, visible_panel)
387 local meta = minetest.get_meta(pos):to_table().fields
389 self.selected = self.selected or 1
390 local selected = tostring(self.selected)
391 local slice_list = minetest.deserialize(meta.slices)
392 local slices = ""
393 for _, i in pairs(slice_list) do
394 local insert = F(S("Y = @1; Probability = @2", tostring(i.ypos), tostring(i.prob)))
395 slices = slices..F(insert)..","
397 slices = slices:sub(1, -2) -- Remove final comma
399 local form = [[
400 size[7,8]
401 table[0,0;6.8,6;slices;]]..slices..[[;]]..selected..[[]
404 if self.panel_add or self.panel_edit then
405 local ypos_default, prob_default = "", ""
406 local done_button = "button[5,7.18;2,1;done_add;"..F(S("Done")).."]"
407 if self.panel_edit then
408 done_button = "button[5,7.18;2,1;done_edit;"..F(S("Done")).."]"
409 if slice_list[self.selected] then
410 ypos_default = slice_list[self.selected].ypos
411 prob_default = slice_list[self.selected].prob
415 form = form..[[
416 field[0.3,7.5;2.5,1;ypos;]]..F(S("Y position (max. @1):", (meta.y_size - 1)))..[[;]]..ypos_default..[[]
417 field[2.8,7.5;2.5,1;prob;]]..F(S("Probability (0-255):"))..[[;]]..prob_default..[[]
418 field_close_on_enter[ypos;false]
419 field_close_on_enter[prob;false]
420 ]]..done_button
423 if not self.panel_edit then
424 form = form.."button[0,6;2,1;add;"..F(S("+ Add slice")).."]"
427 if slices ~= "" and self.selected and not self.panel_add then
428 if not self.panel_edit then
429 form = form..[[
430 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
431 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
433 else
434 form = form..[[
435 button[2,6;2,1;remove;]]..F(S("- Remove slice"))..[[]
436 button[4,6;2,1;edit;]]..F(S("+/- Edit slice"))..[[]
441 return form
442 end,
443 handle = function(self, pos, name, fields)
444 local meta = minetest.get_meta(pos)
445 local player = minetest.get_player_by_name(name)
447 if fields.slices then
448 local slices = fields.slices:split(":")
449 self.selected = tonumber(slices[2])
452 if fields.add then
453 if not self.panel_add then
454 self.panel_add = true
455 schemedit.show_formspec(pos, player, "slice")
456 else
457 self.panel_add = nil
458 schemedit.show_formspec(pos, player, "slice")
462 local ypos, prob = tonumber(fields.ypos), tonumber(fields.prob)
463 if (fields.done_add or fields.done_edit) and fields.ypos and fields.prob and
464 fields.ypos ~= "" and fields.prob ~= "" and ypos and prob and
465 ypos <= (meta:get_int("y_size") - 1) and prob >= 0 and prob <= 255 then
466 local slice_list = minetest.deserialize(meta:get_string("slices"))
467 local index = #slice_list + 1
468 if fields.done_edit then
469 index = self.selected
472 slice_list[index] = {ypos = ypos, prob = prob}
474 meta:set_string("slices", minetest.serialize(slice_list))
476 -- Update and show formspec
477 self.panel_add = nil
478 schemedit.show_formspec(pos, player, "slice")
481 if fields.remove and self.selected then
482 local slice_list = minetest.deserialize(meta:get_string("slices"))
483 slice_list[self.selected] = nil
484 meta:set_string("slices", minetest.serialize(renumber(slice_list)))
486 -- Update formspec
487 self.selected = 1
488 self.panel_edit = nil
489 schemedit.show_formspec(pos, player, "slice")
492 if fields.edit then
493 if not self.panel_edit then
494 self.panel_edit = true
495 schemedit.show_formspec(pos, player, "slice")
496 else
497 self.panel_edit = nil
498 schemedit.show_formspec(pos, player, "slice")
501 end,
504 schemedit.add_form("probtool", {
505 cache_name = false,
506 caption = S("Schematic Node Probability Tool"),
507 get = function(self, pos, name)
508 local player = minetest.get_player_by_name(name)
509 if not player then
510 return
512 local probtool = player:get_wielded_item()
513 if probtool:get_name() ~= "schemedit:probtool" then
514 return
517 local meta = probtool:get_meta()
518 local prob = tonumber(meta:get_string("schemedit_prob"))
519 local force_place = meta:get_string("schemedit_force_place")
521 if not prob then
522 prob = 255
524 if force_place == nil or force_place == "" then
525 force_place = "false"
527 local form = "size[5,4]"..
528 "label[0,0;"..F(S("Schematic Node Probability Tool")).."]"..
529 "field[0.75,1;4,1;prob;"..F(S("Probability (0-255)"))..";"..prob.."]"..
530 "checkbox[0.60,1.5;force_place;"..F(S("Force placement"))..";" .. force_place .. "]" ..
531 "button_exit[0.25,3;2,1;cancel;"..F(S("Cancel")).."]"..
532 "button_exit[2.75,3;2,1;submit;"..F(S("Apply")).."]"..
533 "tooltip[prob;"..F(S("Probability that the node will be placed")).."]"..
534 "tooltip[force_place;"..F(S("If enabled, the node will replace nodes other than air and ignore")).."]"..
535 "field_close_on_enter[prob;false]"
536 return form
537 end,
538 handle = function(self, pos, name, fields)
539 if fields.submit then
540 local prob = tonumber(fields.prob)
541 if prob then
542 local player = minetest.get_player_by_name(name)
543 if not player then
544 return
546 local probtool = player:get_wielded_item()
547 if probtool:get_name() ~= "schemedit:probtool" then
548 return
551 local force_place = self.force_place == true
553 set_item_metadata(probtool, prob, force_place)
555 -- Repurpose the tool's wear bar to display the set probability
556 probtool:set_wear(math.floor(((255-prob)/255)*65535))
558 player:set_wielded_item(probtool)
561 if fields.force_place == "true" then
562 self.force_place = true
563 elseif fields.force_place == "false" then
564 self.force_place = false
566 end,
570 --- API
573 --- Copies and modifies positions `pos1` and `pos2` so that each component of
574 -- `pos1` is less than or equal to the corresponding component of `pos2`.
575 -- Returns the new positions.
576 function schemedit.sort_pos(pos1, pos2)
577 if not pos1 or not pos2 then
578 return
581 pos1, pos2 = table.copy(pos1), table.copy(pos2)
582 if pos1.x > pos2.x then
583 pos2.x, pos1.x = pos1.x, pos2.x
585 if pos1.y > pos2.y then
586 pos2.y, pos1.y = pos1.y, pos2.y
588 if pos1.z > pos2.z then
589 pos2.z, pos1.z = pos1.z, pos2.z
591 return pos1, pos2
594 -- [function] Prepare size
595 function schemedit.size(pos)
596 local pos1 = vector.new(pos)
597 local meta = minetest.get_meta(pos)
598 local node = minetest.get_node(pos)
599 local param2 = node.param2
600 local size = {
601 x = meta:get_int("x_size"),
602 y = math.max(meta:get_int("y_size") - 1, 0),
603 z = meta:get_int("z_size"),
606 if param2 == 1 then
607 local new_pos = vector.add({x = size.z, y = size.y, z = -size.x}, pos)
608 pos1.x = pos1.x + 1
609 new_pos.z = new_pos.z + 1
610 return pos1, new_pos
611 elseif param2 == 2 then
612 local new_pos = vector.add({x = -size.x, y = size.y, z = -size.z}, pos)
613 pos1.z = pos1.z - 1
614 new_pos.x = new_pos.x + 1
615 return pos1, new_pos
616 elseif param2 == 3 then
617 local new_pos = vector.add({x = -size.z, y = size.y, z = size.x}, pos)
618 pos1.x = pos1.x - 1
619 new_pos.z = new_pos.z - 1
620 return pos1, new_pos
621 else
622 local new_pos = vector.add(size, pos)
623 pos1.z = pos1.z + 1
624 new_pos.x = new_pos.x - 1
625 return pos1, new_pos
629 -- [function] Mark region
630 function schemedit.mark(pos)
631 schemedit.unmark(pos)
633 local id = minetest.hash_node_position(pos)
634 local owner = minetest.get_meta(pos):get_string("owner")
635 local pos1, pos2 = schemedit.size(pos)
636 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
638 local thickness = 0.2
639 local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
640 local m = {}
641 local low = true
642 local offset
644 -- XY plane markers
645 for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
646 if low then
647 offset = -0.01
648 else
649 offset = 0.01
651 local marker = minetest.add_entity({x = pos1.x + sizex - 0.5, y = pos1.y + sizey - 0.5, z = z + offset}, "schemedit:display")
652 if marker ~= nil then
653 marker:set_properties({
654 visual_size={x=(sizex+0.01) * 2, y=sizey * 2},
656 marker:get_luaentity().id = id
657 marker:get_luaentity().owner = owner
658 table.insert(m, marker)
660 low = false
663 low = true
664 -- YZ plane markers
665 for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
666 if low then
667 offset = -0.01
668 else
669 offset = 0.01
672 local marker = minetest.add_entity({x = x + offset, y = pos1.y + sizey - 0.5, z = pos1.z + sizez - 0.5}, "schemedit:display")
673 if marker ~= nil then
674 marker:set_properties({
675 visual_size={x=(sizez+0.01) * 2, y=sizey * 2},
677 marker:set_yaw(math.pi / 2)
678 marker:get_luaentity().id = id
679 marker:get_luaentity().owner = owner
680 table.insert(m, marker)
682 low = false
685 schemedit.markers[id] = m
686 return true
689 -- [function] Unmark region
690 function schemedit.unmark(pos)
691 local id = minetest.hash_node_position(pos)
692 if schemedit.markers[id] then
693 local retval
694 for _, entity in ipairs(schemedit.markers[id]) do
695 entity:remove()
696 retval = true
698 return retval
703 --- Mark node probability values near player
706 -- Show probability and force_place status of a particular position for player in HUD.
707 -- Probability is shown as a number followed by “[F]” if the node is force-placed.
708 -- The distance to the node is also displayed below that. This can't be avoided and is
709 -- and artifact of the waypoint HUD element. TODO: Hide displayed distance.
710 function schemedit.display_node_prob(player, pos, prob, force_place)
711 local wpstring
712 if prob and force_place == true then
713 wpstring = string.format("%d [F]", prob)
714 elseif prob then
715 wpstring = prob
716 elseif force_place == true then
717 wpstring = "[F]"
719 if wpstring then
720 return player:hud_add({
721 hud_elem_type = "waypoint",
722 name = wpstring,
723 text = "m", -- For the distance artifact
724 number = text_color_number,
725 world_pos = pos,
730 -- Display the node probabilities and force_place status of the nodes in a region.
731 -- By default, this is done for nodes near the player (distance: 5).
732 -- But the boundaries can optionally be set explicitly with pos1 and pos2.
733 function schemedit.display_node_probs_region(player, pos1, pos2)
734 local playername = player:get_player_name()
735 local pos = vector.round(player:get_pos())
737 local dist = 5
738 -- Default: 5 nodes away from player in any direction
739 if not pos1 then
740 pos1 = vector.subtract(pos, dist)
742 if not pos2 then
743 pos2 = vector.add(pos, dist)
745 for x=pos1.x, pos2.x do
746 for y=pos1.y, pos2.y do
747 for z=pos1.z, pos2.z do
748 local checkpos = {x=x, y=y, z=z}
749 local nodehash = minetest.hash_node_position(checkpos)
751 -- If node is already displayed, remove it so it can re replaced later
752 if displayed_waypoints[playername][nodehash] then
753 player:hud_remove(displayed_waypoints[playername][nodehash])
754 displayed_waypoints[playername][nodehash] = nil
757 local prob, force_place
758 local meta = minetest.get_meta(checkpos)
759 prob = tonumber(meta:get_string("schemedit_prob"))
760 force_place = meta:get_string("schemedit_force_place") == "true"
761 local hud_id = schemedit.display_node_prob(player, checkpos, prob, force_place)
762 if hud_id then
763 displayed_waypoints[playername][nodehash] = hud_id
764 displayed_waypoints[playername].display_active = true
771 -- Remove all active displayed node statuses.
772 function schemedit.clear_displayed_node_probs(player)
773 local playername = player:get_player_name()
774 for nodehash, hud_id in pairs(displayed_waypoints[playername]) do
775 player:hud_remove(hud_id)
776 displayed_waypoints[playername][nodehash] = nil
777 displayed_waypoints[playername].display_active = false
781 minetest.register_on_joinplayer(function(player)
782 displayed_waypoints[player:get_player_name()] = {
783 display_active = false -- If true, there *might* be at least one active node prob HUD display
784 -- If false, no node probabilities are displayed for sure.
786 end)
788 minetest.register_on_leaveplayer(function(player)
789 displayed_waypoints[player:get_player_name()] = nil
790 end)
792 -- Regularily clear the displayed node probabilities and force_place
793 -- for all players who do not wield the probtool.
794 -- This makes sure the screen is not spammed with information when it
795 -- isn't needed.
796 local cleartimer = 0
797 minetest.register_globalstep(function(dtime)
798 cleartimer = cleartimer + dtime
799 if cleartimer > 2 then
800 local players = minetest.get_connected_players()
801 for p = 1, #players do
802 local player = players[p]
803 local pname = player:get_player_name()
804 if displayed_waypoints[pname].display_active then
805 local item = player:get_wielded_item()
806 if item:get_name() ~= "schemedit:probtool" then
807 schemedit.clear_displayed_node_probs(player)
811 cleartimer = 0
813 end)
816 --- Registrations
819 -- [priv] schematic_override
820 minetest.register_privilege("schematic_override", {
821 description = S("Allows you to access schemedit nodes not owned by you"),
822 give_to_singleplayer = false,
825 -- [node] Schematic creator
826 minetest.register_node("schemedit:creator", {
827 description = S("Schematic Creator"),
828 _doc_items_longdesc = S("The schematic creator is used to save a region of the world into a schematic file (.mts)."),
829 _doc_items_usagehelp = S("To get started, place the block facing directly in front of any bottom left corner of the structure you want to save. This block can only be accessed by the placer or by anyone with the “schematic_override” privilege.").."\n"..
830 S("To save a region, rightclick the block, enter the size, a schematic name and hit “Export schematic”. The file will always be saved in the world directory. Note you can use this name in the /placeschem command to place the schematic again.").."\n\n"..
831 S("The other features of the schematic creator are optional and are used to allow to add randomness and fine-tuning.").."\n\n"..
832 S("Y slices are used to remove entire slices based on chance. For each slice of the schematic region along the Y axis, you can specify that it occours only with a certain chance. In the Y slice tab, you have to specify the Y slice height (0 = bottom) and a probability from 0 to 255 (255 is for 100%). By default, all Y slices occour always.").."\n\n"..
833 S("With a schematic node probability tool, you can set a probability for each node and enable them to overwrite all nodes when placed as schematic. This tool must be used prior to the file export."),
834 tiles = {"schemedit_creator_top.png", "schemedit_creator_bottom.png",
835 "schemedit_creator_sides.png"},
836 groups = { dig_immediate = 2},
837 paramtype2 = "facedir",
838 is_ground_content = false,
840 after_place_node = function(pos, player)
841 local name = player:get_player_name()
842 local meta = minetest.get_meta(pos)
844 meta:set_string("owner", name)
845 meta:set_string("infotext", S("Schematic Creator").."\n"..S("(owned by @1)", name))
846 meta:set_string("prob_list", minetest.serialize({}))
847 meta:set_string("slices", minetest.serialize({}))
849 local node = minetest.get_node(pos)
850 local dir = minetest.facedir_to_dir(node.param2)
852 meta:set_int("x_size", 1)
853 meta:set_int("y_size", 1)
854 meta:set_int("z_size", 1)
856 -- Don't take item from itemstack
857 return true
858 end,
859 can_dig = function(pos, player)
860 local name = player:get_player_name()
861 local meta = minetest.get_meta(pos)
862 if meta:get_string("owner") == name or
863 minetest.check_player_privs(player, "schematic_override") == true then
864 return true
867 return false
868 end,
869 on_rightclick = function(pos, node, player)
870 local meta = minetest.get_meta(pos)
871 local name = player:get_player_name()
872 if meta:get_string("owner") == name or
873 minetest.check_player_privs(player, "schematic_override") == true then
874 -- Get player attribute
875 local tab = player:get_attribute("schemedit:tab")
876 if not forms[tab] or not tab then
877 tab = "main"
880 schemedit.show_formspec(pos, player, tab, true)
882 end,
883 after_destruct = function(pos)
884 schemedit.unmark(pos)
885 end,
888 minetest.register_tool("schemedit:probtool", {
889 description = S("Schematic Node Probability Tool"),
890 _doc_items_longdesc =
891 S("This is an advanced tool which only makes sense when used together with a schematic creator. It is used to finetune the way how nodes from a schematic are placed.").."\n"..
892 S("It allows you to set two things:").."\n"..
893 S("1) Set probability: Chance for any particular node to be actually placed (default: always placed)").."\n"..
894 S("2) Enable force placement: These nodes replace node other than air and ignored when placed in a schematic (default: off)"),
895 _doc_items_usagehelp = "\n"..
896 S("BASIC USAGE:").."\n"..
897 S("Punch to configure the tool. Select a probability (0-255; 255 is for 100%) and enable or disable force placement. Now place the tool on any node to apply these values to the node. This information is preserved in the node until it is destroyed or changed by the tool again. This tool has no effect on schematic voids.").."\n"..
898 S("Now you can use a schematic creator to save a region as usual, the nodes will now be saved with the special node settings applied.").."\n\n"..
899 S("NODE HUD:").."\n"..
900 S("To help you remember the node values, the nodes with special values are labelled in the HUD. The first line shows probability and force placement (with “[F]”). The second line is the current distance to the node. Nodes with default settings and schematic voids are not labelled.").."\n"..
901 S("To disable the node HUD, unselect the tool or hit “place” while not pointing anything.").."\n\n"..
902 S("UPDATING THE NODE HUD:").."\n"..
903 S("The node HUD is not updated automatically any may be outdated. The node HUD only updates the HUD for nodes close to you whenever you place the tool or press the punch and sneak keys simutanously. If you sneak-punch a schematic creator, then the node HUd is updated for all nodes within the schematic creator's region, even if this region is very big."),
904 wield_image = "schemedit_probtool.png",
905 inventory_image = "schemedit_probtool.png",
906 liquids_pointable = true,
907 groups = { disable_repair = 1 },
908 on_use = function(itemstack, user, pointed_thing)
909 local ctrl = user:get_player_control()
910 -- Simple use
911 if not ctrl.sneak then
912 -- Open dialog to change the probability to apply to nodes
913 schemedit.show_formspec(user:get_pos(), user, "probtool", true)
915 -- Use + sneak
916 else
917 -- Display the probability and force_place values for nodes.
919 -- If a schematic creator was punched, only enable display for all nodes
920 -- within the creator's region.
921 local use_creator_region = false
922 if pointed_thing and pointed_thing.type == "node" and pointed_thing.under then
923 punchpos = pointed_thing.under
924 local node = minetest.get_node(punchpos)
925 if node.name == "schemedit:creator" then
926 local pos1, pos2 = schemedit.size(punchpos)
927 pos1, pos2 = schemedit.sort_pos(pos1, pos2)
928 schemedit.display_node_probs_region(user, pos1, pos2)
929 return
933 -- Otherwise, just display the region close to the player
934 schemedit.display_node_probs_region(user)
936 end,
937 on_secondary_use = function(itemstack, user, pointed_thing)
938 schemedit.clear_displayed_node_probs(user)
939 end,
940 -- Set note probability and force_place and enable node probability display
941 on_place = function(itemstack, placer, pointed_thing)
942 -- Use pointed node's on_rightclick function first, if present
943 local node = minetest.get_node(pointed_thing.under)
944 if placer and not placer:get_player_control().sneak then
945 if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
946 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
950 -- This sets the node probability of pointed node to the
951 -- currently used probability stored in the tool.
952 local pos = pointed_thing.under
953 local node = minetest.get_node(pos)
954 -- Schematic void are ignored, they always have probability 0
955 if node.name == "schemedit:void" then
956 return itemstack
958 local nmeta = minetest.get_meta(pos)
959 local imeta = itemstack:get_meta()
960 local prob = tonumber(imeta:get_string("schemedit_prob"))
961 local force_place = imeta:get_string("schemedit_force_place")
963 if not prob or prob == 255 then
964 nmeta:set_string("schemedit_prob", nil)
965 else
966 nmeta:set_string("schemedit_prob", prob)
968 if force_place == "true" then
969 nmeta:set_string("schemedit_force_place", "true")
970 else
971 nmeta:set_string("schemedit_force_place", nil)
974 -- Enable node probablity display
975 schemedit.display_node_probs_region(placer)
977 return itemstack
978 end,
981 minetest.register_node("schemedit:void", {
982 description = S("Schematic Void"),
983 _doc_items_longdesc = S("This is an utility block used in the creation of schematic files. It should be used together with a schematic creator. When saving a schematic, all nodes with a schematic void will be left unchanged when the schematic is placed again. Technically, this is equivalent to a block with the node probability set to 0."),
984 _doc_items_usagehelp = S("Just place the schematic void like any other block and use the schematic creator to save a portion of the world."),
985 tiles = { "schemedit_void.png" },
986 drawtype = "nodebox",
987 is_ground_content = false,
988 paramtype = "light",
989 walkable = false,
990 sunlight_propagates = true,
991 node_box = {
992 type = "fixed",
993 fixed = {
994 { -4/16, -4/16, -4/16, 4/16, 4/16, 4/16 },
997 groups = { dig_immediate = 3},
1000 -- [entity] Visible schematic border
1001 minetest.register_entity("schemedit:display", {
1002 visual = "upright_sprite",
1003 textures = {"schemedit_border.png"},
1004 visual_size = {x=10, y=10},
1005 pointable = false,
1006 physical = false,
1007 glow = minetest.LIGHT_MAX,
1009 on_step = function(self, dtime)
1010 if not self.id then
1011 self.object:remove()
1012 elseif not schemedit.markers[self.id] then
1013 self.object:remove()
1015 end,
1016 on_activate = function(self)
1017 self.object:set_armor_groups({immortal = 1})
1018 end,
1021 -- [chatcommand] Place schematic
1022 minetest.register_chatcommand("placeschem", {
1023 description = S("Place schematic at the position specified or the current player position (loaded from @1)", export_path_trunc),
1024 privs = {debug = true},
1025 params = S("<schematic name>[.mts] [<x> <y> <z>]"),
1026 func = function(name, param)
1027 local schem, p = string.match(param, "^([^ ]+) *(.*)$")
1028 local pos = minetest.string_to_pos(p)
1030 if not schem then
1031 return false, S("No schematic file specified.")
1034 if not pos then
1035 pos = minetest.get_player_by_name(name):get_pos()
1038 -- Automatiically add file name suffix if omitted
1039 local schem_full
1040 if string.sub(schem, string.len(schem)-3, string.len(schem)) == ".mts" then
1041 schem_full = schem
1042 else
1043 schem_full = schem .. ".mts"
1046 local success = minetest.place_schematic(pos, export_path_full .. DIR_DELIM .. schem_full, "random", nil, false)
1048 if success == nil then
1049 return false, S("Schematic file could not be loaded!")
1050 else
1051 return true
1053 end,