Version 1.6.1
[minetest_ltool.git] / init.lua
blobb6c6734c39f0f0d4765ae8c4663ce824dd674620
1 local S = minetest.get_translator("ltool")
2 local N = function(s) return s end
3 local F = minetest.formspec_escape
5 ltool = {}
7 ltool.VERSION = {}
8 ltool.VERSION.MAJOR = 1
9 ltool.VERSION.MINOR = 6
10 ltool.VERSION.PATCH = 1
11 ltool.VERSION.STRING = ltool.VERSION.MAJOR .. "." .. ltool.VERSION.MINOR .. "." .. ltool.VERSION.PATCH
13 ltool.playerinfos = {}
14 ltool.default_edit_fields = {
15 axiom="",
16 rules_a="",
17 rules_b="",
18 rules_c="",
19 rules_d="",
20 trunk="mapgen_tree",
21 leaves="mapgen_leaves",
22 leaves2="mapgen_jungleleaves",
23 leaves2_chance="0",
24 fruit="mapgen_apple",
25 fruit_chance="0",
26 angle="45",
27 iterations="2",
28 random_level="0",
29 trunk_type="single",
30 thin_branches="true",
31 name = "",
34 local mod_select_item = minetest.get_modpath("select_item") ~= nil
36 local sapling_base_name = S("L-System Tree Sapling")
37 local sapling_format_string = N("L-System Tree Sapling (@1)")
39 local place_tree = function(pos)
40 -- Place tree
41 local meta = minetest.get_meta(pos)
42 local treedef = minetest.deserialize(meta:get_string("treedef"))
43 minetest.remove_node(pos)
44 minetest.spawn_tree(pos, treedef)
45 end
47 --[[ This registers the sapling for planting the trees ]]
48 minetest.register_node("ltool:sapling", {
49 description = sapling_base_name,
50 _doc_items_longdesc = S("This artificial sapling does not come from nature and contains the genome of a genetically engineered L-system tree. Every sapling of this kind is unique. Who knows what might grow from it when you plant it?"),
51 _doc_items_usagehelp = S("Place the sapling on any floor and wait 5 seconds for the tree to appear. If you have the “lplant” privilege, you can grow it instantly by using it. If you hold down the sneak key while placing it, you will keep a copy of the sapling in your inventory.").."\n"..S("To create your own saplings, you need to have the “lplant” privilege and pick a tree from the L-System Tree Utility (accessed with the server command “treeform”)."),
52 drawtype = "plantlike",
53 tiles = { "ltool_sapling.png" },
54 inventory_image = "ltool_sapling.png",
55 selection_box = {
56 type = "fixed",
57 fixed = { -10/32, -0.5, -10/32, 10/32, 12/32, 10/32 },
59 wield_image = "ltool_sapling.png",
60 paramtype = "light",
61 paramtype2= "wallmounted",
62 walkable = false,
63 groups = { dig_immediate = 3, not_in_creative_inventory=1, },
64 drop = "",
65 sunlight_propagates = true,
66 is_ground_content = false,
67 after_place_node = function(pos, placer, itemstack, pointed_thing)
68 -- Transfer metadata and start timer
69 local nodemeta = minetest.get_meta(pos)
70 local itemmeta = itemstack:get_meta()
71 local itemtreedef = itemmeta:get_string("treedef")
73 -- Legacy support for saplings with legacy metadata
74 if itemtreedef == nil or itemtreedef == "" then
75 itemtreedef = itemstack:get_metadata()
76 if itemtreedef == nil or itemtreedef == "" then
77 return nil
78 end
79 end
80 nodemeta:set_string("treedef", itemtreedef)
81 local timer = minetest.get_node_timer(pos)
82 timer:start(5)
83 if placer:get_player_control().sneak == true then
84 return true
85 else
86 return nil
87 end
88 end,
89 -- Insta-grow when sapling got rightclicked
90 on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
91 if minetest.get_player_privs(clicker:get_player_name()).lplant then
92 place_tree(pos)
93 end
94 end,
95 -- Grow after timer elapsed
96 on_timer = place_tree,
97 can_dig = function(pos, player)
98 return minetest.get_player_privs(player:get_player_name()).lplant
99 end,
102 minetest.register_craftitem("ltool:tool", {
103 description = S("L-System Tree Utility"),
104 _doc_items_longdesc = S("This gadget allows the aspiring genetic engineer to invent and change L-system trees, create L-system tree saplings and look at the inventions from other players. L-system trees are trees and tree-like strucures which are built by a set of (possibly recursive) production rules."),
105 _doc_items_usagehelp = S("Punch to open the L-System editor. A tabbed form will open. To edit and create trees, you need the “ledit” privilege, to make saplings, you need “lplant”. Detailed usage help can be found in that menu. You can also access the same editor with the server command “treeform”."),
106 inventory_image = "ltool_tool.png",
107 wield_image = "ltool_tool.png",
108 on_use = function(itemstack, user, pointed_thing)
109 ltool.show_treeform(user:get_player_name())
110 end,
113 --[[ Register privileges ]]
114 minetest.register_privilege("ledit", {
115 description = S("Can add, edit, rename and delete own L-system tree definitions of the ltool mod"),
116 give_to_singleplayer = false,
118 minetest.register_privilege("lplant", {
119 description = S("Can place L-system trees and get L-system tree saplings of the ltool mod"),
120 give_to_singleplayer = false,
123 --[[ Load previously saved data from file or initialize an empty tree table ]]
125 local filepath = minetest.get_worldpath().."/ltool.mt"
126 local file = io.open(filepath, "r")
127 if(file) then
128 local string = file:read()
129 io.close(file)
130 if(string ~= nil) then
131 local savetable = minetest.deserialize(string)
132 if(savetable ~= nil) then
133 ltool.trees = savetable.trees
134 ltool.next_tree_id = savetable.next_tree_id
135 ltool.number_of_trees = savetable.number_of_trees
136 minetest.log("action", "[ltool] Tree data loaded from "..filepath..".")
137 else
138 minetest.log("error", "[ltool] Failed to load tree data from "..filepath..".")
140 else
141 minetest.log("error", "[ltool] Failed to load tree data from "..filepath..".")
143 else
144 --[[ table of all trees ]]
145 ltool.trees = {}
146 --[[ helper variables to ensure unique IDs ]]
147 ltool.number_of_trees = 0
148 ltool.next_tree_id = 1
152 --[[ Adds a tree to the tree table.
153 name: The tree’s name.
154 author: The author’s / owners’ name
155 treedef: The full tree definition, see lua_api.txt
157 returns the tree ID of the new tree
159 function ltool.add_tree(name, author, treedef)
160 local id = ltool.next_tree_id
161 ltool.trees[id] = {name = name, author = author, treedef = treedef}
162 ltool.next_tree_id = ltool.next_tree_id + 1
163 ltool.number_of_trees = ltool.number_of_trees + 1
164 return id
167 --[[ Removes a tree from the database
168 tree_id: ID of the tree to be removed
170 returns nil
172 function ltool.remove_tree(tree_id)
173 ltool.trees[tree_id] = nil
174 ltool.number_of_trees = ltool.number_of_trees - 1
175 for k,v in pairs(ltool.playerinfos) do
176 if(v.dbsel ~= nil) then
177 if(v.dbsel > ltool.number_of_trees) then
178 v.dbsel = ltool.number_of_trees
180 if(v.dbsel < 1) then
181 v.dbsel = 1
187 --[[ Renames a tree in the database
188 tree_id: ID of the tree to be renamed
189 new_name: The name of the tree
191 returns nil
193 function ltool.rename_tree(tree_id, new_name)
194 ltool.trees[tree_id].name = new_name
197 --[[ Copies a tree in the database
198 tree_id: ID of the tree to be copied
200 returns: the ID of the copy on success;
201 false on failure (tree does not exist)
203 function ltool.copy_tree(tree_id)
204 local tree = ltool.trees[tree_id]
205 if(tree == nil) then
206 return false
208 return ltool.add_tree(tree.name, tree.author, tree.treedef)
211 --[[ Gives a L-system tree sapling to a player
212 treedef: L-system tree definition table of tree the sapling will grow
213 seed: Seed of the tree (optional; can be nil)
214 playername: name of the player to which
215 ignore_priv: if true, player’s lplant privilige is not checked (optional argument; default: false)
216 treename: Descriptive name of the tree for the item description (optional, is ignored if nil or empty string)
218 returns:
219 true on success
220 false, 1 if privilege is not sufficient
221 false, 2 if player’s inventory is full
223 function ltool.give_sapling(treedef, seed, player_name, ignore_priv, treename)
224 local privs = minetest.get_player_privs(player_name)
225 if(ignore_priv == nil) then ignore_priv = false end
226 if(ignore_priv == false and privs.lplant ~= true) then
227 return false, 1
230 local sapling = ItemStack("ltool:sapling")
231 local player = minetest.get_player_by_name(player_name)
232 treedef.seed = seed
233 local smeta = sapling:get_meta()
234 smeta:set_string("treedef", minetest.serialize(treedef))
235 if treename and treename ~= "" then
236 smeta:set_string("description", S(sapling_format_string, treename))
238 treedef.seed = nil
239 local leftover = player:get_inventory():add_item("main", sapling)
240 if(not leftover:is_empty()) then
241 return false, 2
242 else
243 return true
247 --[[ Plants a tree as the specified position
248 tree_id: ID of tree to be planted
249 pos: Position of tree, in format {x=?, y=?, z=?}
250 seed: Optional seed for randomness, equal seed makes equal trees
252 returns false on failure, nil otherwise
254 function ltool.plant_tree(tree_id, pos, seed)
255 local tree = ltool.trees[tree_id]
256 if(tree==nil) then
257 return false
259 local treedef
260 if seed ~= nil then
261 treedef = table.copy(tree.treedef)
262 treedef.seed = seed
263 else
264 treedef = tree.treedef
266 minetest.spawn_tree(pos, treedef)
269 --[[ Tries to return a tree data structure for a given tree_id
271 tree_id: ID of tee to be returned
273 returns false on failure, a tree otherwise
275 function ltool.get_tree(tree_id)
276 local tree = ltool.trees[tree_id]
277 if(tree==nil) then
278 return false
280 return tree
284 ltool.seed = os.time()
287 --[=[ Here come the functions to build the main formspec.
288 They do not build the entire formspec ]=]
290 ltool.formspec_size = "size[12,9]"
292 --[[ This is a part of the main formspec: Tab header ]]
293 function ltool.formspec_header(index)
294 return "tabheader[0,0;ltool_tab;"..F(S("Edit"))..","..F(S("Database"))..","..F(S("Plant"))..","..F(S("Help"))..";"..tostring(index)..";true;false]"
297 --[[ This creates the edit tab of the formspec
298 fields: A template used to fill the default values of the formspec. ]]
299 function ltool.tab_edit(fields, has_ledit_priv, has_lplant_priv)
300 if(fields==nil) then
301 fields = ltool.default_edit_fields
303 local s = function(input)
304 local ret
305 if(input==nil) then
306 ret = ""
307 else
308 ret = F(tostring(input))
310 return ret
313 -- Show save/clear buttons depending on privs
314 local leditbuttons
315 if has_ledit_priv then
316 leditbuttons = "button[0,8.7;4,0;edit_save;"..F(S("Save tree to database")).."]"..
317 "button[4,8.7;4,0;edit_clear;"..F(S("Reset fields")).."]"
318 if has_lplant_priv then
319 leditbuttons = leditbuttons .. "button[8,8.7;4,0;edit_sapling;"..F(S("Generate sapling")).."]"
321 else
322 leditbuttons = "label[0,8.3;"..F(S("Read-only mode. You need the “ledit” privilege to save trees to the database.")).."]"
325 local nlength = "3"
326 local fields_select_item = ""
327 if mod_select_item then
328 nlength = "2.6"
329 fields_select_item = ""..
330 "button[2.4,5.7;0.5,0;edit_trunk;"..F(S(">")).."]"..
331 "button[5.4,5.7;0.5,0;edit_leaves;"..F(S(">")).."]"..
332 "button[8.4,5.7;0.5,0;edit_leaves2;"..F(S(">")).."]"..
333 "button[11.4,5.7;0.5,0;edit_fruit;"..F(S(">")).."]"..
334 "tooltip[edit_trunk;"..F(S("Select node")).."]"..
335 "tooltip[edit_leaves;"..F(S("Select node")).."]"..
336 "tooltip[edit_leaves2;"..F(S("Select node")).."]"..
337 "tooltip[edit_fruit;"..F(S("Select node")).."]"
340 local trunk_type_mapping_reverse = {
341 ["single"] = 1,
342 ["double"] = 2,
343 ["crossed"] = 3,
345 local trunk_type_idx
346 if fields.trunk_type then
347 trunk_type_idx = trunk_type_mapping_reverse[fields.trunk_type]
348 else
349 trunk_type_idx = 1
352 return ""..
353 "field[0.2,1;11,0;axiom;"..F(S("Axiom"))..";"..s(fields.axiom).."]"..
354 "button[11,0.7;1,0;edit_axiom;"..F(S("+")).."]"..
355 "tooltip[edit_axiom;"..F(S("Opens larger text field for Axiom")).."]"..
356 "field[0.2,2;11,0;rules_a;"..F(S("Rules set A"))..";"..s(fields.rules_a).."]"..
357 "button[11,1.7;1,0;edit_rules_a;"..F(S("+")).."]"..
358 "tooltip[edit_rules_a;"..F(S("Opens larger text field for Rules set A")).."]"..
359 "field[0.2,3;11,0;rules_b;"..F(S("Rules set B"))..";"..s(fields.rules_b).."]"..
360 "button[11,2.7;1,0;edit_rules_b;"..F(S("+")).."]"..
361 "tooltip[edit_rules_b;"..F(S("Opens larger text field for Rules set B")).."]"..
362 "field[0.2,4;11,0;rules_c;"..F(S("Rules set C"))..";"..s(fields.rules_c).."]"..
363 "button[11,3.7;1,0;edit_rules_c;"..F(S("+")).."]"..
364 "tooltip[edit_rules_c;"..F(S("Opens larger text field for Rules set C")).."]"..
365 "field[0.2,5;11,0;rules_d;"..F(S("Rules set D"))..";"..s(fields.rules_d).."]"..
366 "button[11,4.7;1,0;edit_rules_d;"..F(S("+")).."]"..
367 "tooltip[edit_rules_d;"..F(S("Opens larger text field for Rules set D")).."]"..
369 "field[0.2,6;"..nlength..",0;trunk;"..F(S("Trunk node"))..";"..s(fields.trunk).."]"..
370 "field[3.2,6;"..nlength..",0;leaves;"..F(S("Leaves node"))..";"..s(fields.leaves).."]"..
371 "field[6.2,6;"..nlength..",0;leaves2;"..F(S("Secondary leaves node"))..";"..s(fields.leaves2).."]"..
372 "field[9.2,6;"..nlength..",0;fruit;"..F(S("Fruit node"))..";"..s(fields.fruit).."]"..
373 fields_select_item..
375 "label[-0.075,5.95;"..F(S("Trunk type")).."]"..
376 "dropdown[-0.075,6.35;3;trunk_type;single,double,crossed;"..trunk_type_mapping_reverse[fields.trunk_type].."]"..
377 "tooltip[trunk_type;"..F(S("Tree trunk type. Possible values:\n- \"single\": trunk of size 1×1\n- \"double\": trunk of size 2×2\n- \"crossed\": trunk in cross shape (3×3).")).."]"..
378 "checkbox[2.9,6.2;thin_branches;"..F(S("Thin branches"))..";"..s(fields.thin_branches).."]"..
379 "tooltip[thin_branches;"..F(S("If enabled, all branches are just 1 node wide, otherwise, branches can be larger.")).."]"..
380 "field[6.2,7;3,0;leaves2_chance;"..F(S("Secondary leaves chance (%)"))..";"..s(fields.leaves2_chance).."]"..
381 "tooltip[leaves2_chance;"..F(S("Chance (in percent) to replace a leaves node by a secondary leaves node")).."]"..
382 "field[9.2,7;3,0;fruit_chance;"..F(S("Fruit chance (%)"))..";"..s(fields.fruit_chance).."]"..
383 "tooltip[fruit_chance;"..F(S("Chance (in percent) to replace a leaves node by a fruit node.")).."]"..
385 "field[0.2,8;3,0;iterations;"..F(S("Iterations"))..";"..s(fields.iterations).."]"..
386 "tooltip[iterations;"..F(S("Maximum number of iterations, usually between 2 and 5.")).."]"..
387 "field[3.2,8;3,0;random_level;"..F(S("Randomness level"))..";"..s(fields.random_level).."]"..
388 "tooltip[random_level;"..F(S("Factor to lower number of iterations, usually between 0 and 3.")).."]"..
389 "field[6.2,8;3,0;angle;"..F(S("Angle (°)"))..";"..s(fields.angle).."]"..
390 "field[9.2,8;3,0;name;"..F(S("Name"))..";"..s(fields.name).."]"..
391 "tooltip[name;"..F(S("Descriptive name for this tree, only used for convenience.")).."]"..
392 leditbuttons
395 --[[ This creates the database tab of the formspec.
396 index: Selected index of the textlist
397 playername: To whom the formspec is shown
399 function ltool.tab_database(index, playername)
400 local treestr, tree_ids = ltool.build_tree_textlist(index, playername)
401 if(treestr ~= nil) then
402 local indexstr
403 if(index == nil) then
404 indexstr = ""
405 else
406 indexstr = tostring(index)
408 ltool.playerinfos[playername].treeform.database.textlist = tree_ids
410 local leditbuttons, lplantbuttons
411 if minetest.get_player_privs(playername).ledit then
412 leditbuttons = "button[3,7.5;3,1;database_rename;"..F(S("Rename tree")).."]"..
413 "button[6,7.5;3,1;database_delete;"..F(S("Delete tree")).."]"
414 else
415 leditbuttons = "label[0.2,7.2;"..F(S("Read-only mode. You need the “ledit” privilege to edit trees.")).."]"
417 if minetest.get_player_privs(playername).lplant then
418 lplantbuttons = "button[0,8.5;3,1;sapling;"..F(S("Generate sapling")).."]"
419 else
420 lplantbuttons = ""
423 return ""..
424 "textlist[0,0;11,7;treelist;"..treestr..";"..tostring(index)..";false]"..
425 lplantbuttons..
426 leditbuttons..
427 "button[3,8.5;3,1;database_copy;"..F(S("Copy tree to editor")).."]"..
428 "button[6,8.5;3,1;database_update;"..F(S("Reload database")).."]"
429 else
430 return "label[0,0;"..F(S("The tree database is empty.")).."]"..
431 "button[6.5,8.5;3,1;database_update;"..F(S("Reload database")).."]"
435 --[[ This creates the "Plant" tab part of the main formspec ]]
436 function ltool.tab_plant(tree, fields, has_lplant_priv)
437 if(tree ~= nil) then
438 local seltree = "label[0,-0.2;"..F(S("Selected tree: @1", tree.name)).."]"
439 if not has_lplant_priv then
440 return seltree..
441 "label[0,0.3;"..F(S("Planting of trees is not allowed. You need to have the “lplant” privilege.")).."]"
443 if(fields==nil) then
444 fields = {}
446 local s = function(i)
447 if(i==nil) then return ""
448 else return tostring(F(i))
451 local seed
452 if(fields.seed == nil) then
453 seed = tostring(ltool.seed)
454 else
455 seed = fields.seed
457 local dropdownindex
458 if(fields.plantmode == F(S("Absolute coordinates"))) then
459 dropdownindex = 1
460 elseif(fields.plantmode == F(S("Relative coordinates"))) then
461 dropdownindex = 2
462 elseif(fields.plantmode == F(S("Distance in viewing direction"))) then
463 dropdownindex = 3
464 else
465 dropdownindex = 1
468 return ""..
469 seltree..
470 "dropdown[-0.1,0.5;5;plantmode;"..F(S("Absolute coordinates"))..","..F(S("Relative coordinates"))..","..F(S("Distance in viewing direction"))..";"..dropdownindex.."]"..
471 --[[ NOTE: This tooltip does not work for the dropdown list in 0.4.10,
472 but it is added anyways in case this gets fixed in later Minetest versions. ]]
473 "tooltip[plantmode;"..
474 F(S("- \"Absolute coordinates\": Fields \"x\", \"y\" and \"z\" specify the absolute world coordinates where to plant the tree")).."\n"..
475 F(S("- \"Relative coordinates\": Fields \"x\", \"y\" and \"z\" specify the relative position from your position")).."\n"..
476 F(S("- \"Distance in viewing direction\": Plant tree relative from your position in the direction you look to, at the specified distance"))..
477 "]"..
478 "field[0.2,-2;6,10;x;"..F(S("x"))..";"..s(fields.x).."]"..
479 "tooltip[x;"..F(S("Field is only used by absolute and relative coordinates.")).."]"..
480 "field[0.2,-1;6,10;y;"..F(S("y"))..";"..s(fields.y).."]"..
481 "tooltip[y;"..F(S("Field is only used by absolute and relative coordinates.")).."]"..
482 "field[0.2,0;6,10;z;"..F(S("z"))..";"..s(fields.z).."]"..
483 "tooltip[z;"..F(S("Field is only used by absolute and relative coordinates.")).."]"..
484 "field[0.2,1;6,10;distance;"..F(S("Distance"))..";"..s(fields.distance).."]"..
485 "tooltip[distance;"..F(S("This field is used to specify the distance (in node lengths) from your position\nin the viewing direction. It is ignored if you use coordinates.")).."]"..
486 "field[0.2,2;6,10;seed;"..F(S("Randomness seed"))..";"..seed.."]"..
487 "tooltip[seed;"..F(S("A number used for the random number generators. Identical randomness seeds will produce identical trees. This field is optional.")).."]"..
488 "button[3.5,8;3,1;plant_plant;"..F(S("Plant tree")).."]"..
489 "tooltip[plant_plant;"..F(S("Immediately place the tree at the specified position")).."]"..
490 "button[6.5,8;3,1;sapling;"..F(S("Generate sapling")).."]"..
491 "tooltip[sapling;"..F(S("This gives you an item which you can place manually in the world later")).."]"
492 else
493 local notreestr = F(S("No tree in database selected or database is empty."))
494 if has_lplant_priv then
495 return "label[0,0;"..notreestr.."]"
496 else
497 return "label[0,0;"..notreestr.."\n"..F(S("You are not allowed to plant trees anyway as you don't have the “lplant” privilege.")).."]"
503 --[[ This creates the cheat sheet tab ]]
504 function ltool.tab_cheat_sheet()
505 return ""..
506 "tablecolumns[text;text]"..
507 "tableoptions[background=#000000;highlight=#000000;border=false]"..
508 "table[-0.15,0.75;12,8;cheat_sheet;"..
509 F(S("Symbol"))..","..F(S("Action"))..","..
510 "G,"..F(S("Move forward one unit with the pen up"))..","..
511 "F,"..F(S("Move forward one unit with the pen down drawing trunks and branches"))..","..
512 "f,"..F(S("Move forward one unit with the pen down drawing leaves"))..","..
513 "T,"..F(S("Move forward one unit with the pen down drawing trunks"))..","..
514 "R,"..F(S("Move forward one unit with the pen down placing fruit"))..","..
515 "A,"..F(S("Replace with rules set A"))..","..
516 "B,"..F(S("Replace with rules set B"))..","..
517 "C,"..F(S("Replace with rules set C"))..","..
518 "D,"..F(S("Replace with rules set D"))..","..
519 "a,"..F(S("Replace with rules set A, chance 90%"))..","..
520 "b,"..F(S("Replace with rules set B, chance 80%"))..","..
521 "c,"..F(S("Replace with rules set C, chance 70%"))..","..
522 "d,"..F(S("Replace with rules set D, chance 60%"))..","..
523 "+,"..F(S("Yaw the turtle right by angle parameter"))..","..
524 "-,"..F(S("Yaw the turtle left by angle parameter"))..","..
525 "&,"..F(S("Pitch the turtle down by angle parameter"))..","..
526 "^,"..F(S("Pitch the turtle up by angle parameter"))..","..
527 "/,"..F(S("Roll the turtle to the right by angle parameter"))..","..
528 "*,"..F(S("Roll the turtle to the left by angle parameter"))..","..
529 "\\[,"..F(S("Save in stack current state info"))..","..
530 "\\],"..F(S("Recover from stack state info")).."]"
533 function ltool.tab_help_intro()
534 return ""..
535 "textarea[0.2,0.75;12,8;;;"..
537 S("You are using the L-System Tree Utility, version @1.", ltool.VERSION.STRING).."\n\n"..
539 S("The purpose of this utility is to aid with the creation of L-system trees. You can create, save, manage and plant L-system trees. All trees are saved into <world path>/ltool.mt on server shutdown.").."\n"..
540 S("It assumes you already understand the concept of L-systems, this utility is mainly aimed towards modders and nerds.").."\n\n"..
542 S("The usual workflow goes like this:").."\n\n"..
544 S("1. Create a new tree in the \"Edit\" tab and save it").."\n"..
545 S("2. Select it in the database").."\n"..
546 S("3. Plant it").."\n\n"..
548 S("To help you get started, you can create an example tree for the \"Edit\" tab by pressing this button:")
549 ).."]"..
550 "button[4,8;4,1;create_template;"..F(S("Create template")).."]"
553 function ltool.tab_help_edit()
554 return ""..
555 "textarea[0.2,0.75;12,9;;;"..
557 S("To create a L-system tree, switch to the \"Edit\" tab.").."\n"..
558 S("When you are done, hit \"Save tree to database\". The tree will be stored in the database. The \"Reset fields\" button resets the input fields to defaults.").."\n\n"..
560 S("To understand the meaning of the fields, read the introduction to L-systems.").."\n\n"..
562 S("All trees must have an unique name. You are notified in case of a name clash. If the name clash is with one of your own trees, you can choose to replace it.")
563 ).."]"
566 function ltool.tab_help_database()
567 return ""..
568 "textarea[0.2,0.75;12,9;;;"..
570 S("The database contains a list of all created trees among all players.").."\n\n"..
572 S("Each tree has an \"owner\". This kind of ownership is limited: The owner may rename, change and delete their own trees, everyone else is prevented from doing that. But all trees can be copied freely by everyone.").."\n"..
573 S("To do so, simply hit \"Copy tree to editor\", change the name and hit \"Save tree to database\". If you like someone else's tree definition, it is recommended to make a copy for yourself, since the original owner can at any time choose to delete or edit the tree. The trees which you \"own\" are written in a yellow font, all other trees in a white font.").."\n\n"..
575 S("In order to plant a tree, you have to select a tree in the database first.")
576 ).."]"
579 function ltool.tab_help_plant()
580 return ""..
581 "textarea[0.2,0.75;12,9;;;"..
583 S("To plant a tree from a previously created tree definition, first select it in the database, then open the \"Plant\" tab. In this tab, you can directly place the tree or request a sapling.").."\n"..
584 S("If you choose to directly place the tree, you can either specify absolute or relative coordinates or specify that the tree should be planted in your viewing direction. Absolute coordinates are the world coordinates as specified by the \"x\", \"y\", and \"z\" fields. Relative coordinates are relative to your position and use the same fields. When you choose to plant the tree based on your viewing direction, the tree will be planted at a distance specified by the field \"distance\" away from you in the direction you look to.").."\n"..
585 S("When using coordinates, the \"distance\" field is ignored, when using direction, the coordinate fields are ignored.").."\n\n"..
587 S("You can also use the “lplant” server command to plant trees.").."\n\n"..
589 S("If you got a sapling, you can place it practically anywhere you like to. After placing it, the sapling will be replaced by the L-system tree after 5 seconds, unless it was destroyed in the meantime.").."\n"..
590 S("All requested saplings are independent from the moment they are created. The sapling will still work, even if the original tree definiton has been deleted.")
591 ).."]"
594 function ltool.tab_help(index)
595 local formspec = "tabheader[0.1,1;ltool_help_tab;"..F(S("Introduction"))..","..F(S("Creating Trees"))..","..F(S("Managing Trees"))..","..F(S("Planting Trees"))..","..F(S("Cheat Sheet"))..";"..tostring(index)..";true;false]"
596 if(index==1) then
597 formspec = formspec .. ltool.tab_help_intro()
598 elseif(index==2) then
599 formspec = formspec .. ltool.tab_help_edit()
600 elseif(index==3) then
601 formspec = formspec .. ltool.tab_help_database()
602 elseif(index==4) then
603 formspec = formspec .. ltool.tab_help_plant()
604 elseif(index==5) then
605 formspec = formspec .. ltool.tab_cheat_sheet()
608 return formspec
611 function ltool.formspec_editplus(fragment)
612 local formspec = ""..
613 "size[12,8]"..
614 "textarea[0.2,0.5;12,3;"..fragment.."]"..
615 "label[0,3.625;"..F(S("Draw:")).."]"..
616 "button[2,3.5;1,1;editplus_c_G;G]"..
617 "tooltip[editplus_c_G;"..F(S("Move forward one unit with the pen up")).."]"..
618 "button[3,3.5;1,1;editplus_c_F;F]"..
619 "tooltip[editplus_c_F;"..F(S("Move forward one unit with the pen down drawing trunks and branches")).."]"..
620 "button[4,3.5;1,1;editplus_c_f;f]"..
621 "tooltip[editplus_c_f;"..F(S("Move forward one unit with the pen down drawing leaves")).."]"..
622 "button[5,3.5;1,1;editplus_c_T;T]"..
623 "tooltip[editplus_c_T;"..F(S("Move forward one unit with the pen down drawing trunks")).."]"..
624 "button[6,3.5;1,1;editplus_c_R;R]"..
625 "tooltip[editplus_c_R;"..F(S("Move forward one unit with the pen down placing fruit")).."]"..
627 "label[0,4.625;"..F(S("Rules:")).."]"..
628 "button[2,4.5;1,1;editplus_c_A;A]"..
629 "tooltip[editplus_c_A;"..F(S("Replace with rules set A")).."]"..
630 "button[3,4.5;1,1;editplus_c_B;B]"..
631 "tooltip[editplus_c_B;"..F(S("Replace with rules set B")).."]"..
632 "button[4,4.5;1,1;editplus_c_C;C]"..
633 "tooltip[editplus_c_C;"..F(S("Replace with rules set C")).."]"..
634 "button[5,4.5;1,1;editplus_c_D;D]"..
635 "tooltip[editplus_c_D;"..F(S("Replace with rules set D")).."]"..
636 "button[6.5,4.5;1,1;editplus_c_a;a]"..
637 "tooltip[editplus_c_a;"..F(S("Replace with rules set A, chance 90%")).."]"..
638 "button[7.5,4.5;1,1;editplus_c_b;b]"..
639 "tooltip[editplus_c_b;"..F(S("Replace with rules set B, chance 80%")).."]"..
640 "button[8.5,4.5;1,1;editplus_c_c;c]"..
641 "tooltip[editplus_c_c;"..F(S("Replace with rules set C, chance 70%")).."]"..
642 "button[9.5,4.5;1,1;editplus_c_d;d]"..
643 "tooltip[editplus_c_d;"..F(S("Replace with rules set D, chance 60%")).."]"..
645 "label[0,5.625;"..F(S("Rotate:")).."]"..
646 "button[3,5.5;1,1;editplus_c_+;+]"..
647 "tooltip[editplus_c_+;"..F(S("Yaw the turtle right by the value specified in \"Angle\"")).."]"..
648 "button[2,5.5;1,1;editplus_c_-;-]"..
649 "tooltip[editplus_c_-;"..F(S("Yaw the turtle left by the value specified in \"Angle\"")).."]"..
650 "button[4.5,5.5;1,1;editplus_c_&;&]"..
651 "tooltip[editplus_c_&;"..F(S("Pitch the turtle down by the value specified in \"Angle\"")).."]"..
652 "button[5.5,5.5;1,1;editplus_c_^;^]"..
653 "tooltip[editplus_c_^;"..F(S("Pitch the turtle up by the value specified in \"Angle\"")).."]"..
654 "button[8,5.5;1,1;editplus_c_/;/]"..
655 "tooltip[editplus_c_/;"..F(S("Roll the turtle to the right by the value specified in \"Angle\"")).."]"..
656 "button[7,5.5;1,1;editplus_c_*;*]"..
657 "tooltip[editplus_c_*;"..F(S("Roll the turtle to the left by the value specified in \"Angle\"")).."]"..
659 "label[0,6.625;"..F(S("Stack:")).."]"..
660 "button[2,6.5;1,1;editplus_c_P;\\[]"..
661 "tooltip[editplus_c_P;"..F(S("Save current state info into stack")).."]"..
662 "button[3,6.5;1,1;editplus_c_p;\\]]"..
663 "tooltip[editplus_c_p;"..F(S("Recover from current stack state info")).."]"..
665 "button[2.5,7.5;3,1;editplus_save;"..F(S("Save")).."]"..
666 "button[5.5,7.5;3,1;editplus_cancel;"..F(S("Cancel")).."]"
668 return formspec
671 --[[ creates the content of a textlist which contains all trees.
672 index: Selected entry
673 playername: To which the main formspec is shown to. Used for highlighting owned trees
675 returns (string to be used in the text list, table of tree IDs)
677 function ltool.build_tree_textlist(index, playername)
678 local string = ""
679 local colorstring
680 if(ltool.number_of_trees == 0) then
681 return nil
683 local tree_ids = ltool.get_tree_ids()
684 for i=1,#tree_ids do
685 local tree_id = tree_ids[i]
686 local tree = ltool.trees[tree_id]
687 if(tree.author == playername) then
688 colorstring = "#FFFF00"
689 else
690 colorstring = ""
692 string = string .. colorstring .. tostring(tree_id) .. ": " .. F(tree.name)
693 if(i~=#tree_ids) then
694 string = string .. ","
697 return string, tree_ids
700 --[=[ Here come functions which show formspecs to players ]=]
702 --[[ Shows the main tree form to the given player, starting with the "Edit" tab ]]
703 function ltool.show_treeform(playername)
704 local privs = minetest.get_player_privs(playername)
705 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
706 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
709 --[[ spawns a simple dialog formspec to a player ]]
710 function ltool.show_dialog(playername, formname, message)
711 local formspec = "size[12,2;]label[0,0.2;"..message.."]"..
712 "button[4.5,1.5;3,1;okay;"..F(S("OK")).."]"
713 minetest.show_formspec(playername, formname, formspec)
718 --[=[ End of formspec-relatec functions ]=]
720 --[[ This function does a lot of parameter checks and returns (tree, tree_name) on success.
721 If ANY parameter check fails, the whole function fails.
722 On failure, it returns (nil, <error message string>).]]
723 function ltool.evaluate_edit_fields(fields, ignore_name)
724 local treedef = {}
725 -- Validation helper: Checks for invalid characters for the fields “axiom” and the 4 rule sets
726 local v = function(str)
727 local match = string.match(str, "[^][abcdfABCDFGTR+-/*&^]")
728 if(match==nil) then
729 return true
730 else
731 return false
734 -- Validation helper: Checks for balanced brackets
735 local b = function(str)
736 local brackets = 0
737 for c=1, string.len(str) do
738 local char = string.sub(str, c, c)
739 if char == "[" then
740 brackets = brackets + 1
741 elseif char == "]" then
742 brackets = brackets - 1
743 if brackets < 0 then
744 return false
748 return brackets == 0
751 if(v(fields.axiom) and v(fields.rules_a) and v(fields.rules_b) and v(fields.rules_c) and v(fields.rules_d)) then
752 if(b(fields.axiom) and b(fields.rules_a) and b(fields.rules_b) and b(fields.rules_c) and b(fields.rules_d)) then
753 treedef.rules_a = fields.rules_a
754 treedef.rules_b = fields.rules_b
755 treedef.rules_c = fields.rules_c
756 treedef.rules_d = fields.rules_d
757 treedef.axiom = fields.axiom
758 else
759 return nil, S("The brackets are unbalanced! For each of the axiom and the rule sets, each opening bracket must be matched by a closing bracket.")
761 else
762 return nil, S("The axiom or one of the rule sets contains at least one invalid character.\nSee the cheat sheet for a list of allowed characters.")
764 treedef.trunk = fields.trunk
765 treedef.leaves = fields.leaves
766 treedef.leaves2 = fields.leaves2
767 treedef.leaves2_chance = fields.leaves2_chance
768 treedef.angle = tonumber(fields.angle)
769 if(treedef.angle == nil) then
770 return nil, S("The field \"Angle\" must contain a number.")
772 treedef.iterations = tonumber(fields.iterations)
773 if(treedef.iterations == nil) then
774 return nil, S("The field \"Iterations\" must contain a natural number greater or equal to 0.")
775 elseif(treedef.iterations < 0) then
776 return nil, S("The field \"Iterations\" must contain a natural number greater or equal to 0.")
778 treedef.random_level = tonumber(fields.random_level)
779 if(treedef.random_level == nil) then
780 return nil, S("The field \"Randomness level\" must contain a number.")
782 treedef.fruit = fields.fruit
783 treedef.fruit_chance = tonumber(fields.fruit_chance)
784 if(treedef.fruit_chance == nil) then
785 return nil, S("The field \"Fruit chance\" must contain a number.")
786 elseif(treedef.fruit_chance > 100 or treedef.fruit_chance < 0) then
787 return nil, S("Fruit chance must be between 0% and 100%.")
789 if(fields.trunk_type == "single" or fields.trunk_type == "double" or fields.trunk_type == "crossed") then
790 treedef.trunk_type = fields.trunk_type
791 else
792 return nil, S("Trunk type must be \"single\", \"double\" or \"crossed\".")
794 treedef.thin_branches = fields.thin_branches
795 if(fields.thin_branches == "true") then
796 treedef.thin_branches = true
797 elseif(fields.thin_branches == "false") then
798 treedef.thin_branches = false
799 else
800 return nil, S("Field \"Thin branches\" must be \"true\" or \"false\".")
802 local name = fields.name
803 if(ignore_name ~= true and name == "") then
804 return nil, S("Name is empty.")
806 return treedef, name
810 --[=[ Here come several utility functions ]=]
812 --[[ converts a given tree to field names, as if they were given to a
813 minetest.register_on_plyer_receive_fields callback function ]]
814 function ltool.tree_to_fields(tree)
815 local s = function(i)
816 if(i==nil) then
817 return ""
818 else
819 return tostring(i)
822 local fields = {}
823 fields.axiom = s(tree.treedef.axiom)
824 fields.rules_a = s(tree.treedef.rules_a)
825 fields.rules_b = s(tree.treedef.rules_b)
826 fields.rules_c = s(tree.treedef.rules_c)
827 fields.rules_d = s(tree.treedef.rules_d)
828 fields.trunk = s(tree.treedef.trunk)
829 fields.leaves = s(tree.treedef.leaves)
830 fields.leaves2 = s(tree.treedef.leaves2)
831 fields.leaves2_chance = s(tree.treedef.leaves2)
832 fields.fruit = s(tree.treedef.fruit)
833 fields.fruit_chance = s(tree.treedef.fruit_chance)
834 fields.angle = s(tree.treedef.angle)
835 fields.iterations = s(tree.treedef.iterations)
836 fields.random_level = s(tree.treedef.random_level)
837 fields.trunk_type = s(tree.treedef.trunk_type)
838 fields.thin_branches = s(tree.treedef.thin_branches)
839 fields.name = s(tree.name)
840 return fields
845 -- returns a simple table of all the tree IDs
846 function ltool.get_tree_ids()
847 local ids = {}
848 for tree_id, _ in pairs(ltool.trees) do
849 table.insert(ids, tree_id)
851 table.sort(ids)
852 return ids
855 --[[ In a table of tree IDs (returned by ltool.get_tree_ids, parameter tree_ids), this function
856 searches for the first occourance of the value searched_tree_id and returns its index.
857 This is basically a reverse lookup utility. ]]
858 function ltool.get_tree_id_index(searched_tree_id, tree_ids)
859 for i=1, #tree_ids do
860 local table_tree_id = tree_ids[i]
861 if(searched_tree_id == table_tree_id) then
862 return i
867 -- Returns the selected tree of the given player
868 function ltool.get_selected_tree(playername)
869 local sel = ltool.playerinfos[playername].dbsel
870 if(sel ~= nil) then
871 local tree_id = ltool.playerinfos[playername].treeform.database.textlist[sel]
872 if(tree_id ~= nil) then
873 return ltool.trees[tree_id]
876 return nil
879 -- Returns the ID of the selected tree of the given player
880 function ltool.get_selected_tree_id(playername)
881 local sel = ltool.playerinfos[playername].dbsel
882 if(sel ~= nil) then
883 return ltool.playerinfos[playername].treeform.database.textlist[sel]
885 return nil
889 ltool.treeform = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit()
891 minetest.register_chatcommand("treeform",
893 params = "",
894 description = "Open L-System Tree Utility.",
895 privs = {},
896 func = function(playername, param)
897 ltool.show_treeform(playername)
901 minetest.register_chatcommand("lplant",
903 description = S("Plant a L-system tree at the specified position"),
904 privs = { lplant = true },
905 params = S("<tree ID> <x> <y> <z> [<seed>]"),
906 func = function(playername, param)
907 local p = {}
908 local tree_id, x, y, z, seed = string.match(param, "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *([%d.-]*)")
909 tree_id, p.x, p.y, p.z, seed = tonumber(tree_id), tonumber(x), tonumber(y), tonumber(z), tonumber(seed)
910 if not tree_id or not p.x or not p.y or not p.z then
911 return false, S("Invalid usage, see /help lplant.")
913 local lm = tonumber(minetest.settings:get("map_generation_limit") or 31000)
914 if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm or p.z < -lm or p.z > lm then
915 return false, S("Cannot plant tree out of map bounds!")
918 local success = ltool.plant_tree(tree_id, p, seed)
919 if success == false then
920 return false, S("Unknown tree ID!")
921 else
922 return true
927 function ltool.dbsel_to_tree(dbsel, playername)
928 return ltool.trees[ltool.playerinfos[playername].treeform.database.textlist[dbsel]]
931 function ltool.save_fields(playername,formname,fields)
932 if not fields.thin_branches then
933 fields.thin_branches = ltool.playerinfos[playername].treeform.edit.thin_branches
935 if(formname=="ltool:treeform_edit") then
936 ltool.playerinfos[playername].treeform.edit.fields = fields
937 elseif(formname=="ltool:treeform_database") then
938 ltool.playerinfos[playername].treeform.database.fields = fields
939 elseif(formname=="ltool:treeform_plant") then
940 ltool.playerinfos[playername].treeform.plant.fields = fields
944 local function handle_sapling_button_database_plant(seltree, seltree_id, privs, formname, fields, playername)
945 if(seltree ~= nil) then
946 if(privs.lplant ~= true) then
947 ltool.save_fields(playername, formname, fields)
948 local message = S("You can't request saplings, you need to have the \"lplant\" privilege.")
949 ltool.show_dialog(playername, "ltool:treeform_error_sapling", message)
950 return false
952 local seed = nil
953 if(tonumber(fields.seed)~=nil) then
954 seed = tonumber(fields.seed)
956 if ltool.trees[seltree_id] then
957 local ret, ret2 = ltool.give_sapling(ltool.trees[seltree_id].treedef, seed, playername, true, ltool.trees[seltree_id].name)
958 if(ret==false and ret2==2) then
959 ltool.save_fields(playername, formname, fields)
960 ltool.show_dialog(playername, "ltool:treeform_error_sapling", S("Error: The sapling could not be given to you. Probably your inventory is full."))
966 --[=[ Callback functions start here ]=]
967 function ltool.process_form(player,formname,fields)
968 local playername = player:get_player_name()
970 local seltree = ltool.get_selected_tree(playername)
971 local seltree_id = ltool.get_selected_tree_id(playername)
972 local privs = minetest.get_player_privs(playername)
973 local s = function(input)
974 local ret
975 if(input==nil) then
976 ret = ""
977 else
978 ret = F(tostring(input))
980 return ret
982 -- Update thin_branches field
983 if(formname == "ltool:treeform_edit") then
984 if(not fields.thin_branches) then
985 fields.thin_branches = ltool.playerinfos[playername].treeform.edit.thin_branches
986 if(not fields.thin_branches) then
987 minetest.log("error", "[ltool] thin_branches field of "..playername.." is nil!")
989 else
990 ltool.playerinfos[playername].treeform.edit.thin_branches = fields.thin_branches
993 --[[ process clicks on the tab header ]]
994 if(formname == "ltool:treeform_edit" or formname == "ltool:treeform_database" or formname == "ltool:treeform_plant" or formname == "ltool:treeform_help") then
995 if fields.ltool_tab ~= nil then
996 ltool.save_fields(playername, formname, fields)
997 local tab = tonumber(fields.ltool_tab)
998 local formspec, subformname, contents
999 if(tab==1) then
1000 contents = ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
1001 subformname = "edit"
1002 elseif(tab==2) then
1003 contents = ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1004 subformname = "database"
1005 elseif(tab==3) then
1006 if(ltool.number_of_trees > 0) then
1007 contents = ltool.tab_plant(seltree, ltool.playerinfos[playername].treeform.plant.fields, privs.lplant)
1008 else
1009 contents = ltool.tab_plant(nil, nil, privs.lplant)
1011 subformname = "plant"
1012 elseif(tab==4) then
1013 contents = ltool.tab_help(ltool.playerinfos[playername].treeform.help.tab)
1014 subformname = "help"
1016 formspec = ltool.formspec_size..ltool.formspec_header(tab)..contents
1017 minetest.show_formspec(playername, "ltool:treeform_" .. subformname, formspec)
1018 return
1021 --[[ "Plant" tab ]]
1022 if(formname == "ltool:treeform_plant") then
1023 if(fields.plant_plant) then
1024 if(seltree ~= nil) then
1025 if(privs.lplant ~= true) then
1026 ltool.save_fields(playername, formname, fields)
1027 local message = S("You can't plant trees, you need to have the \"lplant\" privilege.")
1028 ltool.show_dialog(playername, "ltool:treeform_error_lplant", message)
1029 return
1031 minetest.log("action","[ltool] Planting tree")
1032 local treedef = seltree.treedef
1034 local x,y,z = tonumber(fields.x), tonumber(fields.y), tonumber(fields.z)
1035 local distance = tonumber(fields.distance)
1036 local tree_pos
1037 local fail_coordinates = function()
1038 ltool.save_fields(playername, formname, fields)
1039 ltool.show_dialog(playername, "ltool:treeform_error_badplantfields", S("Error: When using coordinates, you have to specify numbers in the fields \"x\", \"y\", \"z\"."))
1041 local fail_distance = function()
1042 ltool.save_fields(playername, formname, fields)
1043 ltool.show_dialog(playername, "ltool:treeform_error_badplantfields", S("Error: When using viewing direction for planting trees,\nyou must specify how far away you want the tree to be placed in the field \"Distance\"."))
1045 if(fields.plantmode == F(S("Absolute coordinates"))) then
1046 if(type(x)~="number" or type(y) ~= "number" or type(z) ~= "number") then
1047 fail_coordinates()
1048 return
1050 tree_pos = {x=x, y=y, z=z}
1051 elseif(fields.plantmode == F(S("Relative coordinates"))) then
1052 if(type(x)~="number" or type(y) ~= "number" or type(z) ~= "number") then
1053 fail_coordinates()
1054 return
1056 tree_pos = player:get_pos()
1057 tree_pos.x = tree_pos.x + x
1058 tree_pos.y = tree_pos.y + y
1059 tree_pos.z = tree_pos.z + z
1060 elseif(fields.plantmode == F(S("Distance in viewing direction"))) then
1061 if(type(distance)~="number") then
1062 fail_distance()
1063 return
1065 tree_pos = vector.round(vector.add(player:get_pos(), vector.multiply(player:get_look_dir(), distance)))
1066 else
1067 minetest.log("error", "[ltool] fields.plantmode = "..tostring(fields.plantmode))
1070 if(tonumber(fields.seed)~=nil) then
1071 treedef.seed = tonumber(fields.seed)
1074 ltool.plant_tree(seltree_id, tree_pos)
1076 treedef.seed = nil
1078 elseif(fields.sapling) then
1079 local ret = handle_sapling_button_database_plant(seltree, seltree_id, privs, formname, fields, playername)
1080 if ret == false then
1081 return
1084 --[[ "Edit" tab ]]
1085 elseif(formname == "ltool:treeform_edit") then
1086 if(fields.edit_save or fields.edit_sapling) then
1087 local param1, param2
1088 param1, param2 = ltool.evaluate_edit_fields(fields, fields.edit_sapling ~= nil)
1089 if(fields.edit_save and privs.ledit ~= true) then
1090 ltool.save_fields(playername, formname, fields)
1091 local message = S("You can't save trees, you need to have the \"ledit\" privilege.")
1092 ltool.show_dialog(playername, "ltool:treeform_error_ledit", message)
1093 return
1095 if(fields.edit_sapling and privs.lplant ~= true) then
1096 ltool.save_fields(playername, formname, fields)
1097 local message = S("You can't request saplings, you need to have the \"lplant\" privilege.")
1098 ltool.show_dialog(playername, "ltool:treeform_error_ledit", message)
1099 return
1101 local tree_ok = true
1102 local treedef, name
1103 if(param1 ~= nil) then
1104 treedef = param1
1105 name = param2
1106 for k,v in pairs(ltool.trees) do
1107 if(fields.edit_save and v.name == name) then
1108 ltool.save_fields(playername, formname, fields)
1109 if(v.author == playername) then
1110 local formspec = "size[6,2;]label[0,0.2;You already have a tree with this name.\nDo you want to replace it?]"..
1111 "button[0,1.5;3,1;replace_yes;"..F(S("Yes")).."]"..
1112 "button[3,1.5;3,1;replace_no;"..F(S("No")).."]"
1113 minetest.show_formspec(playername, "ltool:treeform_replace", formspec)
1114 else
1115 ltool.show_dialog(playername, "ltool:treeform_error_nameclash", S("Error: This name is already taken by someone else."))
1117 return
1120 else
1121 tree_ok = false
1123 ltool.save_fields(playername, formname, fields)
1124 if(tree_ok == true) then
1125 if fields.edit_save then
1126 ltool.add_tree(name, playername, treedef)
1127 elseif fields.edit_sapling then
1128 local ret, ret2 = ltool.give_sapling(treedef, tostring(ltool.seed), playername, true, fields.name)
1129 if(ret==false and ret2==2) then
1130 ltool.save_fields(playername, formname, fields)
1131 ltool.show_dialog(playername, "ltool:treeform_error_sapling", S("Error: The sapling could not be given to you. Probably your inventory is full."))
1134 else
1135 local message = S("Error: The tree definition is invalid.").."\n"..
1136 F(param2)
1137 ltool.show_dialog(playername, "ltool:treeform_error_badtreedef", message)
1140 if(fields.edit_clear) then
1141 local privs = minetest.get_player_privs(playername)
1142 ltool.save_fields(playername, formname, ltool.default_edit_fields)
1143 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.default_edit_fields, privs.ledit, privs.lplant)
1145 --[[ hacky_spaces is part of a workaround, see comment on hacky_spaces in ltool.join.
1146 This workaround will slightly change the formspec by adding 0-5 spaces
1147 to the end, changing the number of spaces on each send. This forces
1148 Minetest to re-send the formspec.
1149 Spaces are completely harmless in a formspec.]]
1150 -- BEGIN OF WORKAROUND
1151 local hacky_spaces = ltool.playerinfos[playername].treeform.hacky_spaces
1152 hacky_spaces = hacky_spaces .. " "
1153 if string.len(hacky_spaces) > 5 then
1154 hacky_spaces = ""
1156 ltool.playerinfos[playername].treeform.hacky_spaces = hacky_spaces
1157 local real_formspec = formspec .. hacky_spaces
1158 -- END OF WORKAROUND
1160 minetest.show_formspec(playername, "ltool:treeform_edit", real_formspec)
1162 if(fields.edit_axiom or fields.edit_rules_a or fields.edit_rules_b or fields.edit_rules_c or fields.edit_rules_d) then
1163 local fragment
1164 if(fields.edit_axiom) then
1165 fragment = "axiom;"..F(S("Axiom"))..";"..s(fields.axiom)
1166 elseif(fields.edit_rules_a) then
1167 fragment = "rules_a;"..F(S("Rules set A"))..";"..s(fields.rules_a)
1168 elseif(fields.edit_rules_b) then
1169 fragment = "rules_b;"..F(S("Rules set B"))..";"..s(fields.rules_b)
1170 elseif(fields.edit_rules_c) then
1171 fragment = "rules_c;"..F(S("Rules set C"))..";"..s(fields.rules_c)
1172 elseif(fields.edit_rules_d) then
1173 fragment = "rules_d;"..F(S("Rules set D"))..";"..s(fields.rules_d)
1176 ltool.save_fields(playername, formname, fields)
1177 local formspec = ltool.formspec_editplus(fragment)
1178 minetest.show_formspec(playername, "ltool:treeform_editplus", formspec)
1180 if(mod_select_item and (fields.edit_trunk or fields.edit_leaves or fields.edit_leaves2 or fields.edit_fruit)) then
1181 ltool.save_fields(playername, formname, fields)
1182 -- Prepare sorting.
1183 -- Move tree, leaves, apple/leafdecay nodes to the beginning
1184 local compare_group, fruit
1185 if fields.edit_trunk then
1186 compare_group = "tree"
1187 elseif fields.edit_leaves or fields.edit_leaves2 then
1188 compare_group = "leaves"
1189 elseif fields.edit_fruit or fields.edit_fruit then
1190 compare_group = "leafdecay"
1191 local alias = minetest.registered_aliases["mapgen_apple"]
1192 if alias and minetest.registered_nodes[alias] then
1193 fruit = alias
1196 select_item.show_dialog(playername, "ltool:node", function(itemstring)
1197 if itemstring ~= "air" and minetest.registered_nodes[itemstring] ~= nil then
1198 return true
1200 end,
1201 function(i1, i2)
1202 if fruit and i1 == fruit then
1203 return true
1205 if fruit and i2 == fruit then
1206 return false
1208 local i1t = minetest.get_item_group(i1, compare_group)
1209 local i2t = minetest.get_item_group(i2, compare_group)
1210 local i1d = minetest.registered_items[i1].description
1211 local i2d = minetest.registered_items[i2].description
1212 local i1nici = minetest.get_item_group(i1, "not_in_creative_inventory")
1213 local i2nici = minetest.get_item_group(i2, "not_in_creative_inventory")
1214 if (i1d == "" and i2d ~= "") then
1215 return false
1216 elseif (i1d ~= "" and i2d == "") then
1217 return true
1219 if (i1nici == 1 and i2nici == 0) then
1220 return false
1221 elseif (i1nici == 0 and i2nici == 1) then
1222 return true
1224 if i1t < i2t then
1225 return false
1226 elseif i1t > i2t then
1227 return true
1229 return i1 < i2
1230 end)
1232 --[[ Larger edit fields for axiom and rules fields ]]
1233 elseif(formname == "ltool:treeform_editplus") then
1234 local editfields = ltool.playerinfos[playername].treeform.edit.fields
1235 local function addchar(c)
1236 local fragment
1237 if(c=="P") then c = "[" end
1238 if(c=="p") then c = "]" end
1239 if(fields.axiom) then
1240 fragment = "axiom;"..F(S("Axiom"))..";"..s(fields.axiom..c)
1241 elseif(fields.rules_a) then
1242 fragment = "rules_a;"..F(S("Rules set A"))..";"..s(fields.rules_a..c)
1243 elseif(fields.rules_b) then
1244 fragment = "rules_b;"..F(S("Rules set B"))..";"..s(fields.rules_b..c)
1245 elseif(fields.rules_c) then
1246 fragment = "rules_c;"..F(S("Rules set C"))..";"..s(fields.rules_c..c)
1247 elseif(fields.rules_d) then
1248 fragment = "rules_d;"..F(S("Rules set D"))..";"..s(fields.rules_d..c)
1250 local formspec = ltool.formspec_editplus(fragment)
1251 minetest.show_formspec(playername, "ltool:treeform_editplus", formspec)
1253 if(fields.editplus_save) then
1254 local function o(writed, writer)
1255 if(writer~=nil) then
1256 return writer
1257 else
1258 return writed
1261 editfields.axiom = o(editfields.axiom, fields.axiom)
1262 editfields.rules_a = o(editfields.rules_a, fields.rules_a)
1263 editfields.rules_b = o(editfields.rules_b, fields.rules_b)
1264 editfields.rules_c = o(editfields.rules_c, fields.rules_c)
1265 editfields.rules_d = o(editfields.rules_d, fields.rules_d)
1266 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1267 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1268 elseif(fields.editplus_cancel or fields.quit) then
1269 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1270 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1271 else
1272 for id, field in pairs(fields) do
1273 if(string.sub(id,1,11) == "editplus_c_") then
1274 local char = string.sub(id,12,12)
1275 addchar(char)
1279 --[[ "Database" tab ]]
1280 elseif(formname == "ltool:treeform_database") then
1281 if(fields.treelist) then
1282 local event = minetest.explode_textlist_event(fields.treelist)
1283 if(event.type == "CHG") then
1284 ltool.playerinfos[playername].dbsel = event.index
1285 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(event.index, playername)
1286 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1288 elseif(fields.database_copy) then
1289 if(seltree ~= nil) then
1290 if(ltool.playerinfos[playername] ~= nil) then
1291 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.tree_to_fields(seltree), privs.ledit, privs.lplant)
1292 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1294 else
1295 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", S("Error: No tree is selected."))
1297 elseif(fields.database_update) then
1298 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1299 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1301 elseif(fields.database_delete) then
1302 if(privs.ledit ~= true) then
1303 ltool.save_fields(playername, formname, fields)
1304 local message = S("You can't delete trees, you need to have the \"ledit\" privilege.")
1305 ltool.show_dialog(playername, "ltool:treeform_error_ledit_db", message)
1306 return
1308 if(seltree ~= nil) then
1309 if(playername == seltree.author) then
1310 local remove_id = ltool.get_selected_tree_id(playername)
1311 if(remove_id ~= nil) then
1312 ltool.remove_tree(remove_id)
1313 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1314 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1316 else
1317 ltool.show_dialog(playername, "ltool:treeform_error_delete", S("Error: This tree is not your own. You may only delete your own trees."))
1319 else
1320 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", S("Error: No tree is selected."))
1322 elseif(fields.database_rename) then
1323 if(seltree ~= nil) then
1324 if(privs.ledit ~= true) then
1325 ltool.save_fields(playername, formname, fields)
1326 local message = S("You can't rename trees, you need to have the \"ledit\" privilege.")
1327 ltool.show_dialog(playername, "ltool:treeform_error_ledit_db", message)
1328 return
1330 if(playername == seltree.author) then
1331 local formspec = "field[newname;"..F(S("New name:"))..";"..F(seltree.name).."]"
1332 minetest.show_formspec(playername, "ltool:treeform_rename", formspec)
1333 else
1334 ltool.show_dialog(playername, "ltool:treeform_error_rename_forbidden", S("Error: This tree is not your own. You may only rename your own trees."))
1336 else
1337 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", S("Error: No tree is selected."))
1339 elseif(fields.sapling) then
1340 local ret = handle_sapling_button_database_plant(seltree, seltree_id, privs, formname, fields, playername)
1341 if ret == false then
1342 return
1345 --[[ Process "Do you want to replace this tree?" dialog ]]
1346 elseif(formname == "ltool:treeform_replace") then
1347 local editfields = ltool.playerinfos[playername].treeform.edit.fields
1348 local newtreedef, newname = ltool.evaluate_edit_fields(editfields)
1349 if(privs.ledit ~= true) then
1350 local message = S("You can't overwrite trees, you need to have the \"ledit\" privilege.")
1351 minetest.show_dialog(playername, "ltool:treeform_error_ledit", message)
1352 return
1354 if(fields.replace_yes) then
1355 for tree_id,tree in pairs(ltool.trees) do
1356 if(tree.name == newname) then
1357 --[[ The old tree is deleted and a
1358 new one with a new ID is created ]]
1359 local new_tree_id = ltool.next_tree_id
1360 ltool.trees[new_tree_id] = {}
1361 ltool.trees[new_tree_id].treedef = newtreedef
1362 ltool.trees[new_tree_id].name = newname
1363 ltool.trees[new_tree_id].author = tree.author
1364 ltool.next_tree_id = ltool.next_tree_id + 1
1365 ltool.trees[tree_id] = nil
1366 ltool.playerinfos[playername].dbsel = ltool.number_of_trees
1370 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1371 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1372 elseif(formname == "ltool:treeform_help") then
1373 local tab = tonumber(fields.ltool_help_tab)
1374 if(tab ~= nil) then
1375 ltool.playerinfos[playername].treeform.help.tab = tab
1376 local formspec = ltool.formspec_size..ltool.formspec_header(4)..ltool.tab_help(tab)
1377 minetest.show_formspec(playername, "ltool:treeform_help", formspec)
1379 if(fields.create_template) then
1380 local newfields = {
1381 axiom="FFFFFAFFBF",
1382 rules_a="[&&&FFFFF&&FFFF][&&&++++FFFFF&&FFFF][&&&----FFFFF&&FFFF]",
1383 rules_b="[&&&++FFFFF&&FFFF][&&&--FFFFF&&FFFF][&&&------FFFFF&&FFFF]",
1384 trunk="mapgen_tree",
1385 leaves="mapgen_leaves",
1386 leaves2_chance="0",
1387 angle="30",
1388 iterations="2",
1389 random_level="0",
1390 trunk_type="single",
1391 thin_branches="true",
1392 fruit_chance="10",
1393 fruit="mapgen_apple",
1394 name = "Example Tree "..ltool.next_tree_id
1396 ltool.save_fields(playername, formname, newfields)
1397 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(newfields, privs.ledit, privs.lplant)
1398 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1400 --[[ Tree renaming dialog ]]
1401 elseif(formname == "ltool:treeform_rename") then
1402 if(privs.ledit ~= true) then
1403 ltool.save_fields(playername, formname, fields)
1404 local message = S("You can't delete trees, you need to have the \"ledit\" privilege.")
1405 ltool.show_dialog(playername, "ltool:treeform_error_ledit_delete", message)
1406 return
1408 if(fields.newname ~= "" and fields.newname ~= nil) then
1409 ltool.rename_tree(ltool.get_selected_tree_id(playername), fields.newname)
1410 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1411 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1412 else
1413 ltool.show_dialog(playername, "ltool:treeform_error_bad_rename", S("Error: This name is empty. The tree name must be non-empty."))
1415 --[[ Here come various error messages to handle ]]
1416 elseif(formname == "ltool:treeform_error_badtreedef" or formname == "ltool:treeform_error_nameclash" or formname == "ltool:treeform_error_ledit") then
1417 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
1418 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1419 elseif(formname == "ltool:treeform_error_badplantfields" or formname == "ltool:treeform_error_sapling" or formname == "ltool:treeform_error_lplant") then
1420 local formspec = ltool.formspec_size..ltool.formspec_header(3)..ltool.tab_plant(seltree, ltool.playerinfos[playername].treeform.plant.fields, privs.lplant)
1421 minetest.show_formspec(playername, "ltool:treeform_plant", formspec)
1422 elseif(formname == "ltool:treeform_error_delete" or formname == "ltool:treeform_error_rename_forbidden" or formname == "ltool:treeform_error_nodbsel" or formname == "ltool:treeform_error_ledit_db") then
1423 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1424 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1425 elseif(formname == "ltool:treeform_error_bad_rename") then
1426 local formspec = "field[newname;"..F(S("New name:"))..";"..F(seltree.name).."]"
1427 minetest.show_formspec(playername, "ltool:treeform_rename", formspec)
1428 else
1429 -- Action for Inventory++ button
1430 if fields.ltool and minetest.get_modpath("inventory_plus") then
1431 ltool.show_treeform(playername)
1432 return
1437 if mod_select_item then
1438 select_item.register_on_select_item(function(playername, dialogname, itemstring)
1439 if dialogname == "ltool:node" then
1440 if itemstring then
1441 local f = ltool.playerinfos[playername].treeform.edit.fields
1442 if f.edit_trunk then
1443 f.trunk = itemstring
1444 elseif f.edit_leaves then
1445 f.leaves = itemstring
1446 elseif f.edit_leaves2 then
1447 f.leaves2 = itemstring
1448 elseif f.edit_fruit then
1449 f.fruit = itemstring
1452 ltool.show_treeform(playername)
1453 return false
1455 end)
1458 --[[ These 2 functions are basically just table initializions and cleanups ]]
1459 function ltool.leave(player)
1460 ltool.playerinfos[player:get_player_name()] = nil
1463 function ltool.join(player)
1464 local infotable = {}
1465 infotable.dbsel = nil
1466 infotable.treeform = {}
1467 infotable.treeform.database = {}
1468 --[[ This table stores a mapping of the textlist IDs in the database formspec and the tree IDs.
1469 It is updated each time ltool.tab_database is called. ]]
1470 infotable.treeform.database.textlist = {}
1471 --[[ the “fields” tables store the values of the input fields of a formspec. It is updated
1472 whenever the formspec is changed, i.e. on tab change ]]
1473 infotable.treeform.database.fields = {}
1474 infotable.treeform.plant = {}
1475 infotable.treeform.plant.fields = {}
1476 infotable.treeform.edit = {}
1477 infotable.treeform.edit.fields = ltool.default_edit_fields
1478 infotable.treeform.edit.thin_branches = "true"
1479 infotable.treeform.help = {}
1480 infotable.treeform.help.tab = 1
1481 --[[ Workaround for annoying bug in Minetest: When you call the identical formspec twice,
1482 Minetest does not send the second one. This is an issue when the player has changed the
1483 input fields in the meanwhile, resetting fields will fail sometimes.
1484 TODO: Remove workaround when not needed anymore. ]]
1485 -- BEGIN OF WORKAROUND
1486 infotable.treeform.hacky_spaces = ""
1487 -- END OF WORKAROUND
1489 ltool.playerinfos[player:get_player_name()] = infotable
1491 -- Add Inventory++ support
1492 if minetest.get_modpath("inventory_plus") then
1493 inventory_plus.register_button(player, "ltool", S("L-System Tree Utility"))
1497 function ltool.save_to_file()
1498 local savetable = {}
1499 savetable.trees = ltool.trees
1500 savetable.number_of_trees = ltool.number_of_trees
1501 savetable.next_tree_id = ltool.next_tree_id
1502 local savestring = minetest.serialize(savetable)
1503 local filepath = minetest.get_worldpath().."/ltool.mt"
1504 local file = io.open(filepath, "w")
1505 if(file) then
1506 file:write(savestring)
1507 io.close(file)
1508 minetest.log("action", "[ltool] Tree data saved to "..filepath..".")
1509 else
1510 minetest.log("error", "[ltool] Failed to write ltool data to "..filepath".")
1515 minetest.register_on_player_receive_fields(ltool.process_form)
1517 minetest.register_on_leaveplayer(ltool.leave)
1519 minetest.register_on_joinplayer(ltool.join)
1521 minetest.register_on_shutdown(ltool.save_to_file)
1523 local button_action = function(player)
1524 ltool.show_treeform(player:get_player_name())
1527 if minetest.get_modpath("unified_inventory") ~= nil then
1528 unified_inventory.register_button("ltool", {
1529 type = "image",
1530 image = "ltool_sapling.png",
1531 tooltip = S("L-System Tree Utility"),
1532 action = button_action,
1536 if minetest.get_modpath("sfinv_buttons") ~= nil then
1537 sfinv_buttons.register_button("ltool", {
1538 title = S("L-System Tree Utility"),
1539 tooltip = S("Invent your own trees and plant them"),
1540 image = "ltool_sapling.png",
1541 action = button_action,