Make thin branches setting to checkbox
[minetest_ltool.git] / init.lua
blob76051bf29d82cb9a4e39bfbaf59f0b2947a6e1c5
1 ltool = {}
3 ltool.VERSION = {}
4 ltool.VERSION.MAJOR = 1
5 ltool.VERSION.MINOR = 4
6 ltool.VERSION.PATCH = 2
7 ltool.VERSION.STRING = ltool.VERSION.MAJOR .. "." .. ltool.VERSION.MINOR .. "." .. ltool.VERSION.PATCH
9 ltool.playerinfos = {}
10 ltool.default_edit_fields = {
11 axiom="",
12 rules_a="",
13 rules_b="",
14 rules_c="",
15 rules_d="",
16 trunk="mapgen_tree",
17 leaves="mapgen_leaves",
18 leaves2="mapgen_jungleleaves",
19 leaves2_chance="0",
20 fruit="mapgen_apple",
21 fruit_chance="0",
22 angle="45",
23 iterations="2",
24 random_level="0",
25 trunk_type="single",
26 thin_branches="true",
27 name = "",
30 local mod_select_item = minetest.get_modpath("select_item") ~= nil
32 local sapling_base_name = "L-System Tree Sapling"
33 local sapling_format_string = "L-System Tree Sapling (%s)"
35 local place_tree = function(pos)
36 -- Place tree
37 local meta = minetest.get_meta(pos)
38 local treedef = minetest.deserialize(meta:get_string("treedef"))
39 minetest.remove_node(pos)
40 minetest.spawn_tree(pos, treedef)
41 end
43 --[[ This registers the sapling for planting the trees ]]
44 minetest.register_node("ltool:sapling", {
45 description = sapling_base_name,
46 _doc_items_longdesc = "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?",
47 _doc_items_usagehelp = "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 rightclicking it. If you hold down the sneak key while placing it, you will keep a copy of the sapling in your inventory. 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”).",
48 drawtype = "plantlike",
49 tiles = { "ltool_sapling.png" },
50 inventory_image = "ltool_sapling.png",
51 selection_box = {
52 type = "fixed",
53 fixed = { -10/32, -0.5, -10/32, 10/32, 12/32, 10/32 },
55 wield_image = "ltool_sapling.png",
56 paramtype = "light",
57 paramtype2= "wallmounted",
58 walkable = false,
59 groups = { dig_immediate = 3, not_in_creative_inventory=1, },
60 drop = "",
61 sunlight_propagates = true,
62 is_ground_content = false,
63 after_place_node = function(pos, placer, itemstack, pointed_thing)
64 -- Transfer metadata and start timer
65 local nodemeta = minetest.get_meta(pos)
66 local itemmeta = itemstack:get_meta()
67 local itemtreedef = itemmeta:get_string("treedef")
69 -- Legacy support for saplings with legacy metadata
70 if itemtreedef == nil or itemtreedef == "" then
71 itemtreedef = itemstack:get_metadata()
72 if itemtreedef == nil or itemtreedef == "" then
73 return nil
74 end
75 end
76 nodemeta:set_string("treedef", itemtreedef)
77 local timer = minetest.get_node_timer(pos)
78 timer:start(5)
79 if placer:get_player_control().sneak == true then
80 return true
81 else
82 return nil
83 end
84 end,
85 -- Insta-grow when sapling got rightclicked
86 on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
87 if minetest.get_player_privs(clicker:get_player_name()).lplant then
88 place_tree(pos)
89 end
90 end,
91 -- Grow after timer elapsed
92 on_timer = place_tree,
93 can_dig = function(pos, player)
94 return minetest.get_player_privs(player:get_player_name()).lplant
95 end,
98 minetest.register_craftitem("ltool:tool", {
99 description = "L-System Tree Utility",
100 _doc_items_longdesc = "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.",
101 _doc_items_usagehelp = "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”.",
102 inventory_image = "ltool_tool.png",
103 wield_image = "ltool_tool.png",
104 on_use = function(itemstack, user, pointed_thing)
105 ltool.show_treeform(user:get_player_name())
106 end,
109 --[[ Register privileges ]]
110 minetest.register_privilege("ledit", {
111 description = "Can add, edit, rename and delete own L-system tree definitions of the ltool mod",
112 give_to_singleplayer = false,
114 minetest.register_privilege("lplant", {
115 description = "Can place L-system trees and get L-system tree samplings of the ltool mod",
116 give_to_singleplayer = false,
119 --[[ Load previously saved data from file or initialize an empty tree table ]]
121 local filepath = minetest.get_worldpath().."/ltool.mt"
122 local file = io.open(filepath, "r")
123 if(file) then
124 local string = file:read()
125 io.close(file)
126 if(string ~= nil) then
127 local savetable = minetest.deserialize(string)
128 if(savetable ~= nil) then
129 ltool.trees = savetable.trees
130 ltool.next_tree_id = savetable.next_tree_id
131 ltool.number_of_trees = savetable.number_of_trees
132 minetest.log("action", "[ltool] Tree data loaded from "..filepath..".")
133 else
134 minetest.log("error", "[ltool] Failed to load tree data from "..filepath..".")
136 else
137 minetest.log("error", "[ltool] Failed to load tree data from "..filepath..".")
139 else
140 --[[ table of all trees ]]
141 ltool.trees = {}
142 --[[ helper variables to ensure unique IDs ]]
143 ltool.number_of_trees = 0
144 ltool.next_tree_id = 1
148 --[[ Adds a tree to the tree table.
149 name: The tree’s name.
150 author: The author’s / owners’ name
151 treedef: The full tree definition, see lua_api.txt
153 returns the tree ID of the new tree
155 function ltool.add_tree(name, author, treedef)
156 local id = ltool.next_tree_id
157 ltool.trees[id] = {name = name, author = author, treedef = treedef}
158 ltool.next_tree_id = ltool.next_tree_id + 1
159 ltool.number_of_trees = ltool.number_of_trees + 1
160 return id
163 --[[ Removes a tree from the database
164 tree_id: ID of the tree to be removed
166 returns nil
168 function ltool.remove_tree(tree_id)
169 ltool.trees[tree_id] = nil
170 ltool.number_of_trees = ltool.number_of_trees - 1
171 for k,v in pairs(ltool.playerinfos) do
172 if(v.dbsel ~= nil) then
173 if(v.dbsel > ltool.number_of_trees) then
174 v.dbsel = ltool.number_of_trees
176 if(v.dbsel < 1) then
177 v.dbsel = 1
183 --[[ Renames a tree in the database
184 tree_id: ID of the tree to be renamed
185 new_name: The name of the tree
187 returns nil
189 function ltool.rename_tree(tree_id, new_name)
190 ltool.trees[tree_id].name = new_name
193 --[[ Copies a tree in the database
194 tree_id: ID of the tree to be copied
196 returns: the ID of the copy on success;
197 false on failure (tree does not exist)
199 function ltool.copy_tree(tree_id)
200 local tree = ltool.trees[tree_id]
201 if(tree == nil) then
202 return false
204 return ltool.add_tree(tree.name, tree.author, tree.treedef)
207 --[[ Gives a L-system tree sapling to a player
208 treedef: L-system tree definition table of tree the sapling will grow
209 seed: Seed of the tree (optional; can be nil)
210 playername: name of the player to which
211 ignore_priv: if true, player’s lplant privilige is not checked (optional argument; default: false)
212 treename: Descriptive name of the tree for the item description (optional, is ignored if nil or empty string)
214 returns:
215 true on success
216 false, 1 if privilege is not sufficient
217 false, 2 if player’s inventory is full
219 function ltool.give_sapling(treedef, seed, player_name, ignore_priv, treename)
220 local privs = minetest.get_player_privs(player_name)
221 if(ignore_priv == nil) then ignore_priv = false end
222 if(ignore_priv == false and privs.lplant ~= true) then
223 return false, 1
226 local sapling = ItemStack("ltool:sapling")
227 local player = minetest.get_player_by_name(player_name)
228 treedef.seed = seed
229 local smeta = sapling:get_meta()
230 smeta:set_string("treedef", minetest.serialize(treedef))
231 if treename and treename ~= "" then
232 smeta:set_string("description", string.format(sapling_format_string, treename))
234 treedef.seed = nil
235 local leftover = player:get_inventory():add_item("main", sapling)
236 if(not leftover:is_empty()) then
237 return false, 2
238 else
239 return true
243 --[[ Plants a tree as the specified position
244 tree_id: ID of tree to be planted
245 pos: Position of tree, in format {x=?, y=?, z=?}
246 seed: Optional seed for randomness, equal seed makes equal trees
248 returns false on failure, nil otherwise
250 function ltool.plant_tree(tree_id, pos, seed)
251 local tree = ltool.trees[tree_id]
252 if(tree==nil) then
253 return false
255 local treedef
256 if seed ~= nil then
257 treedef = table.copy(tree.treedef)
258 treedef.seed = seed
259 else
260 treedef = tree.treedef
262 minetest.spawn_tree(pos, treedef)
265 --[[ Tries to return a tree data structure for a given tree_id
267 tree_id: ID of tee to be returned
269 returns false on failure, a tree otherwise
271 function ltool.get_tree(tree_id)
272 local tree = ltool.trees[tree_id]
273 if(tree==nil) then
274 return false
276 return tree
280 ltool.seed = os.time()
283 --[=[ Here come the functions to build the main formspec.
284 They do not build the entire formspec ]=]
286 ltool.formspec_size = "size[12,9]"
288 --[[ This is a part of the main formspec: Tab header ]]
289 function ltool.formspec_header(index)
290 return "tabheader[0,0;ltool_tab;Edit,Database,Plant,Help;"..tostring(index)..";true;false]"
293 --[[ This creates the edit tab of the formspec
294 fields: A template used to fill the default values of the formspec. ]]
295 function ltool.tab_edit(fields, has_ledit_priv, has_lplant_priv)
296 if(fields==nil) then
297 fields = ltool.default_edit_fields
299 local s = function(input)
300 local ret
301 if(input==nil) then
302 ret = ""
303 else
304 ret = minetest.formspec_escape(tostring(input))
306 return ret
309 -- Show save/clear buttons depending on privs
310 local leditbuttons
311 if has_ledit_priv then
312 leditbuttons = "button[0,8.7;4,0;edit_save;Save tree to database]"..
313 "button[4,8.7;4,0;edit_clear;Reset fields]"
314 if has_lplant_priv then
315 leditbuttons = leditbuttons .. "button[8,8.7;4,0;edit_sapling;Generate sapling]"
317 else
318 leditbuttons = "label[0,8.3;Read-only mode. You need the “ledit” privilege to save trees to the database.]"
321 local nlength = "3"
322 local fields_select_item = ""
323 if mod_select_item then
324 nlength = "2.6"
325 fields_select_item = ""..
326 "button[2.4,5.7;0.5,0;edit_trunk;>]"..
327 "button[5.4,5.7;0.5,0;edit_leaves;>]"..
328 "button[8.4,5.7;0.5,0;edit_leaves2;>]"..
329 "button[11.4,5.7;0.5,0;edit_fruit;>]"..
330 "tooltip[edit_trunk;Select node]"..
331 "tooltip[edit_leaves;Select node]"..
332 "tooltip[edit_leaves2;Select node]"..
333 "tooltip[edit_fruit;Select node]"
336 local trunk_type_mapping_reverse = {
337 ["single"] = 1,
338 ["double"] = 2,
339 ["crossed"] = 3,
341 local trunk_type_idx
342 if fields.trunk_type then
343 trunk_type_idx = trunk_type_mapping_reverse[fields.trunk_type]
344 else
345 trunk_type_idx = 1
348 return ""..
349 "field[0.2,1;11,0;axiom;Axiom;"..s(fields.axiom).."]"..
350 "button[11,0.7;1,0;edit_axiom;+]"..
351 "tooltip[edit_axiom;Opens larger text field for Axiom]"..
352 "field[0.2,2;11,0;rules_a;Rules set A;"..s(fields.rules_a).."]"..
353 "button[11,1.7;1,0;edit_rules_a;+]"..
354 "tooltip[edit_rules_a;Opens larger text field for Rules set A]"..
355 "field[0.2,3;11,0;rules_b;Rules set B;"..s(fields.rules_b).."]"..
356 "button[11,2.7;1,0;edit_rules_b;+]"..
357 "tooltip[edit_rules_b;Opens larger text field for Rules set B]"..
358 "field[0.2,4;11,0;rules_c;Rules set C;"..s(fields.rules_c).."]"..
359 "button[11,3.7;1,0;edit_rules_c;+]"..
360 "tooltip[edit_rules_c;Opens larger text field for Rules set C]"..
361 "field[0.2,5;11,0;rules_d;Rules set D;"..s(fields.rules_d).."]"..
362 "button[11,4.7;1,0;edit_rules_d;+]"..
363 "tooltip[edit_rules_d;Opens larger text field for Rules set D]"..
365 "field[0.2,6;"..nlength..",0;trunk;Trunk node;"..s(fields.trunk).."]"..
366 "field[3.2,6;"..nlength..",0;leaves;Leaves node;"..s(fields.leaves).."]"..
367 "field[6.2,6;"..nlength..",0;leaves2;Secondary leaves node;"..s(fields.leaves2).."]"..
368 "field[9.2,6;"..nlength..",0;fruit;Fruit node;"..s(fields.fruit).."]"..
369 fields_select_item..
371 "dropdown[-0.075,6.35;3;trunk_type;single,double,crossed;"..trunk_type_mapping_reverse[fields.trunk_type].."]"..
372 "tooltip[trunk_type;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).]"..
373 "checkbox[2.9,6.2;thin_branches;Thin branches;"..s(fields.thin_branches).."]"..
374 "tooltip[thin_branches;If enabled\\, all branches are just 1 node wide\\, otherwise, branches can be larger.]"..
375 "field[6.2,7;3,0;leaves2_chance;Secondary leaves chance (%);"..s(fields.leaves2_chance).."]"..
376 "tooltip[leaves2_chance;Chance (in percent) to replace a leaves node by a secondary leaves node]"..
377 "field[9.2,7;3,0;fruit_chance;Fruit chance (%);"..s(fields.fruit_chance).."]"..
378 "tooltip[fruit_chance;Chance (in percent) to replace a leaves node by a fruit node.]"..
380 "field[0.2,8;3,0;iterations;Iterations;"..s(fields.iterations).."]"..
381 "tooltip[iterations;Maximum number of iterations, usually between 2 and 5.]"..
382 "field[3.2,8;3,0;random_level;Randomness level;"..s(fields.random_level).."]"..
383 "tooltip[random_level;Factor to lower number of iterations, usually between 0 and 3.]"..
384 "field[6.2,8;3,0;angle;Angle (°);"..s(fields.angle).."]"..
385 "field[9.2,8;3,0;name;Name;"..s(fields.name).."]"..
386 "tooltip[name;Descriptive name for this tree, only used for convenience.]"..
387 leditbuttons
390 --[[ This creates the database tab of the formspec.
391 index: Selected index of the textlist
392 playername: To whom the formspec is shown
394 function ltool.tab_database(index, playername)
395 local treestr, tree_ids = ltool.build_tree_textlist(index, playername)
396 if(treestr ~= nil) then
397 local indexstr
398 if(index == nil) then
399 indexstr = ""
400 else
401 indexstr = tostring(index)
403 ltool.playerinfos[playername].treeform.database.textlist = tree_ids
405 local leditbuttons, lplantbuttons
406 if minetest.get_player_privs(playername).ledit then
407 leditbuttons = "button[3,7.5;3,1;database_rename;Rename tree]"..
408 "button[6,7.5;3,1;database_delete;Delete tree]"
409 else
410 leditbuttons = "label[0.2,7.2;Read-only mode. You need the “ledit” privilege to edit trees.]"
412 if minetest.get_player_privs(playername).lplant then
413 lplantbuttons = "button[0,8.5;3,1;sapling;Generate sapling]"
414 else
415 lplantbuttons = ""
418 return ""..
419 "textlist[0,0;11,7;treelist;"..treestr..";"..tostring(index)..";false]"..
420 lplantbuttons..
421 leditbuttons..
422 "button[3,8.5;3,1;database_copy;Copy tree to editor]"..
423 "button[6,8.5;3,1;database_update;Reload database]"
424 else
425 return "label[0,0;The tree database is empty.]"..
426 "button[6.5,8.5;3,1;database_update;Reload database]"
430 --[[ This creates the "Plant" tab part of the main formspec ]]
431 function ltool.tab_plant(tree, fields, has_lplant_priv)
432 if(tree ~= nil) then
433 local seltree = "label[0,-0.2;Selected tree: "..minetest.formspec_escape(tree.name).."]"
434 if not has_lplant_priv then
435 return seltree..
436 "label[0,0.3;Planting of trees is not allowed. You need to have the “lplant” privilege.]"
438 if(fields==nil) then
439 fields = {}
441 local s = function(i)
442 if(i==nil) then return ""
443 else return tostring(minetest.formspec_escape(i))
446 local seed
447 if(fields.seed == nil) then
448 seed = tostring(ltool.seed)
449 else
450 seed = fields.seed
452 local dropdownindex
453 if(fields.plantmode == "Absolute coordinates") then
454 dropdownindex = 1
455 elseif(fields.plantmode == "Relative coordinates") then
456 dropdownindex = 2
457 elseif(fields.plantmode == "Distance in viewing direction") then
458 dropdownindex = 3
459 else
460 dropdownindex = 1
463 return ""..
464 seltree..
465 "dropdown[-0.1,0.5;5;plantmode;Absolute coordinates,Relative coordinates,Distance in viewing direction;"..dropdownindex.."]"..
466 --[[ NOTE: This tooltip does not work for the dropdown list in 0.4.10,
467 but it is added anyways in case this gets fixed in later Minetest versions. ]]
468 "tooltip[plantmode;"..
469 "- \"Absolute coordinates\": Fields \"x\", \"y\" and \"z\" specify the absolute world coordinates where to plant the tree\n"..
470 "- \"Relative coordinates\": Fields \"x\", \"y\" and \"z\" specify the relative position from your position\n"..
471 "- \"Distance in viewing direction\": Plant tree relative from your position in the direction you look to, at the specified distance"..
472 "]"..
473 "field[0.2,-2;6,10;x;x;"..s(fields.x).."]"..
474 "tooltip[x;Field is only used by absolute and relative coordinates.]"..
475 "field[0.2,-1;6,10;y;y;"..s(fields.y).."]"..
476 "tooltip[y;Field is only used by absolute and relative coordinates.]"..
477 "field[0.2,0;6,10;z;z;"..s(fields.z).."]"..
478 "tooltip[z;Field is only used by absolute and relative coordinates.]"..
479 "field[0.2,1;6,10;distance;Distance;"..s(fields.distance).."]"..
480 "tooltip[distance;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.]"..
481 "field[0.2,2;6,10;seed;Randomness seed;"..seed.."]"..
482 "tooltip[seed;A number used for the random number generators. Identical randomness seeds will produce identical trees. This field is optional.]"..
483 "button[3.5,8;3,1;plant_plant;Plant tree]"..
484 "tooltip[plant_plant;Immediately place the tree at the specified position]"..
485 "button[6.5,8;3,1;sapling;Generate sapling]"..
486 "tooltip[sapling;This gives you an item which you can place manually in the world later]"
487 else
488 local notreestr = "No tree in database selected or database is empty."
489 if has_lplant_priv then
490 return "label[0,0;"..notreestr.."]"
491 else
492 return "label[0,0;"..notreestr.."\nYou are not allowed to plant trees anyway as you don't have the “lplant” privilege.]"
498 --[[ This creates the cheat sheet tab ]]
499 function ltool.tab_cheat_sheet()
500 return ""..
501 "tablecolumns[text;text]"..
502 "tableoptions[background=#000000;highlight=#000000;border=false]"..
503 "table[-0.15,0.75;12,8;cheat_sheet;"..
504 "Symbol,Action,"..
505 "G,Move forward one unit with the pen up,"..
506 "F,Move forward one unit with the pen down drawing trunks and branches,"..
507 "f,Move forward one unit with the pen down drawing leaves,"..
508 "T,Move forward one unit with the pen down drawing trunks,"..
509 "R,Move forward one unit with the pen down placing fruit,"..
510 "A,Replace with rules set A,"..
511 "B,Replace with rules set B,"..
512 "C,Replace with rules set C,"..
513 "D,Replace with rules set D,"..
514 "a,Replace with rules set A\\, chance 90%,"..
515 "b,Replace with rules set B\\, chance 80%,"..
516 "c,Replace with rules set C\\, chance 70%,"..
517 "d,Replace with rules set D\\, chance 60%,"..
518 "+,Yaw the turtle right by angle parameter,"..
519 "-,Yaw the turtle left by angle parameter,"..
520 "&,Pitch the turtle down by angle parameter,"..
521 "^,Pitch the turtle up by angle parameter,"..
522 "/,Roll the turtle to the right by angle parameter,"..
523 "*,Roll the turtle to the left by angle parameter,"..
524 "\\[,Save in stack current state info,"..
525 "\\],Recover from stack state info]"
528 function ltool.tab_help_intro()
529 return ""..
530 "tablecolumns[text]"..
531 "tableoptions[background=#000000;highlight=#000000;border=false]"..
532 "table[-0.15,0.75;12,7;help_intro;"..
533 string.format("You are using the L-System Tree Utility, version %s.,", ltool.VERSION.STRING)..
534 ","..
535 "The purpose of this utility is to aid with the creation of L-system trees.,"..
536 "You can create\\, save\\, manage and plant L-system trees.,"..
537 "All trees are saved into <world path>/ltool.mt on server shutdown.,"..
538 "It assumes you already understand the concept of L-systems\\;,"..
539 "this utility is mainly aimed towards modders and nerds.,"..
540 ","..
541 "The usual workflow goes like this:,"..
542 ","..
543 "1. Create a new tree in the \"Edit\" tab and save it,"..
544 "2. Select it in the database,"..
545 "3. Plant it,"..
546 ","..
547 "To help you get started\\, you can create an example tree for the \"Edit\" tab,"..
548 "by pressing this button:]"..
549 "button[4,8;4,1;create_template;Create template]"
552 function ltool.tab_help_edit()
553 return ""..
554 "tablecolumns[text]"..
555 "tableoptions[background=#000000;highlight=#000000;border=false]"..
556 "table[-0.15,0.75;12,8;help_edit;"..
557 "To create a L-system tree\\, switch to the \"Edit\" tab.,"..
558 "When you are done\\, hit \"Save tree to database\". The tree will be stored in,"..
559 "the database. The \"Reset fields\" button resets the input fields to defaults.,"..
560 "To understand the meaning of the fields\\, read the introduction to L-systems.,"..
561 "All trees must have an unique name. You are notified in case there is a name,"..
562 "clash. If the name clash is with one of your own trees\\, you can choose to,"..
563 "replace it.]"
566 function ltool.tab_help_database()
567 return ""..
568 "tablecolumns[text]"..
569 "tableoptions[background=#000000;highlight=#000000;border=false]"..
570 "table[-0.15,0.75;12,8;help_database;"..
571 "The database contains a list of all created trees among all players.,"..
572 "Each tree has an \"owner\". This kind of ownership is limited:,"..
573 "The owner may rename\\, change and delete their own trees\\,,"..
574 "everyone else is prevented from doing that. But all trees can be,"..
575 "copied freely by everyone\\;,"..
576 "To do so\\, simply hit \"Copy tree to editor\"\\, change the name and hit,"..
577 "\"Save tree to database\". If you like someone else's tree definition\\,,"..
578 "it is recommended to make a copy for yourself\\, since the original owner,"..
579 "can at any time choose to delete or edit the tree. The trees which you \"own\","..
580 "are written in a yellow font\\, all other trees in a white font.,"..
581 "In order to plant a tree\\, you have to select a tree in the database first.]"
584 function ltool.tab_help_plant()
585 return ""..
586 "tablecolumns[text]"..
587 "tableoptions[background=#000000;highlight=#000000;border=false]"..
588 "table[-0.15,0.75;12,8;help_plant;"..
589 "To plant a tree from a previously created tree definition\\, first select,"..
590 "it in the database\\, then open the \"Plant\" tab.,"..
591 "In this tab\\, you can directly place the tree or request a sapling.,"..
592 "If you choose to directly place the tree\\, you can either specify absolute,"..
593 "or relative coordinates or specify that the tree should be planted in your,"..
594 "viewing direction. Absolute coordinates are the world coordinates as specified,"..
595 "by the \"x\"\\, \"y\"\\, and \"z\" fields. Relative coordinates are relative,"..
596 "to your position and use the same fields. When you choose to plant the tree,"..
597 "based on your viewing direction\\, the tree will be planted at a distance,"..
598 "specified by the field \"distance\" away from you in the direction you look to.,"..
599 "When using coordinates\\, the \"distance\" field is ignored\\, when using,"..
600 "direction\\, the coordinate fields are ignored.,"..
601 ","..
602 "You can also use the “lplant” server command to plant trees.,"..
603 ","..
604 "If you got a sapling\\, you can place it practically anywhere you like to.,"..
605 "After placing it\\, the sapling will be replaced by the L-system tree after,"..
606 "5 seconds\\, unless it was destroyed in the meantime.,"..
607 "All requested saplings are independent from the moment they are created.,"..
608 "The sapling will still work\\, even if the original tree definiton has been,"..
609 "deleted.]"
612 function ltool.tab_help(index)
613 local formspec = "tabheader[0.1,1;ltool_help_tab;Introduction,Creating Trees,Managing Trees,Planting Trees,Cheat Sheet;"..tostring(index)..";true;false]"
614 if(index==1) then
615 formspec = formspec .. ltool.tab_help_intro()
616 elseif(index==2) then
617 formspec = formspec .. ltool.tab_help_edit()
618 elseif(index==3) then
619 formspec = formspec .. ltool.tab_help_database()
620 elseif(index==4) then
621 formspec = formspec .. ltool.tab_help_plant()
622 elseif(index==5) then
623 formspec = formspec .. ltool.tab_cheat_sheet()
626 return formspec
629 function ltool.formspec_editplus(fragment)
630 local formspec = ""..
631 "size[12,8]"..
632 "textarea[0.2,0.5;12,3;"..fragment.."]"..
633 "label[0,3.625;Draw:]"..
634 "button[2,3.5;1,1;editplus_c_G;G]"..
635 "tooltip[editplus_c_G;Move forward one unit with the pen up]"..
636 "button[3,3.5;1,1;editplus_c_F;F]"..
637 "tooltip[editplus_c_F;Move forward one unit with the pen down drawing trunks and branches]"..
638 "button[4,3.5;1,1;editplus_c_f;f]"..
639 "tooltip[editplus_c_f;Move forward one unit with the pen down drawing leaves]"..
640 "button[5,3.5;1,1;editplus_c_T;T]"..
641 "tooltip[editplus_c_T;Move forward one unit with the pen down drawing trunks]"..
642 "button[6,3.5;1,1;editplus_c_R;R]"..
643 "tooltip[editplus_c_R;Move forward one unit with the pen down placing fruit]"..
645 "label[0,4.625;Rules:]"..
646 "button[2,4.5;1,1;editplus_c_A;A]"..
647 "tooltip[editplus_c_A;Replace with rules set A]"..
648 "button[3,4.5;1,1;editplus_c_B;B]"..
649 "tooltip[editplus_c_B;Replace with rules set B]"..
650 "button[4,4.5;1,1;editplus_c_C;C]"..
651 "tooltip[editplus_c_C;Replace with rules set C]"..
652 "button[5,4.5;1,1;editplus_c_D;D]"..
653 "tooltip[editplus_c_D;Replace with rules set D]"..
654 "button[6.5,4.5;1,1;editplus_c_a;a]"..
655 "tooltip[editplus_c_a;Replace with rules set A, chance 90%]"..
656 "button[7.5,4.5;1,1;editplus_c_b;b]"..
657 "tooltip[editplus_c_b;Replace with rules set B, chance 80%]"..
658 "button[8.5,4.5;1,1;editplus_c_c;c]"..
659 "tooltip[editplus_c_c;Replace with rules set C, chance 70%]"..
660 "button[9.5,4.5;1,1;editplus_c_d;d]"..
661 "tooltip[editplus_c_d;Replace with rules set D, chance 60%]"..
663 "label[0,5.625;Rotate:]"..
664 "button[3,5.5;1,1;editplus_c_+;+]"..
665 "tooltip[editplus_c_+;Yaw the turtle right by the value specified in \"Angle\"]"..
666 "button[2,5.5;1,1;editplus_c_-;-]"..
667 "tooltip[editplus_c_-;Yaw the turtle left by the value specified in \"Angle\"]"..
668 "button[4.5,5.5;1,1;editplus_c_&;&]"..
669 "tooltip[editplus_c_&;Pitch the turtle down by the value specified in \"Angle\"]"..
670 "button[5.5,5.5;1,1;editplus_c_^;^]"..
671 "tooltip[editplus_c_^;Pitch the turtle up by the value specified in \"Angle\"]"..
672 "button[8,5.5;1,1;editplus_c_/;/]"..
673 "tooltip[editplus_c_/;Roll the turtle to the right by the value specified in \"Angle\"]"..
674 "button[7,5.5;1,1;editplus_c_*;*]"..
675 "tooltip[editplus_c_*;Roll the turtle to the left by the value specified in \"Angle\"]"..
677 "label[0,6.625;Stack:]"..
678 "button[2,6.5;1,1;editplus_c_P;\\[]"..
679 "tooltip[editplus_c_P;Save current state info into stack]"..
680 "button[3,6.5;1,1;editplus_c_p;\\]]"..
681 "tooltip[editplus_c_p;Recover from current stack state info]"..
683 "button[2.5,7.5;3,1;editplus_save;Save]"..
684 "button[5.5,7.5;3,1;editplus_cancel;Cancel]"
686 return formspec
689 --[[ creates the content of a textlist which contains all trees.
690 index: Selected entry
691 playername: To which the main formspec is shown to. Used for highlighting owned trees
693 returns (string to be used in the text list, table of tree IDs)
695 function ltool.build_tree_textlist(index, playername)
696 local string = ""
697 local colorstring
698 if(ltool.number_of_trees == 0) then
699 return nil
701 local tree_ids = ltool.get_tree_ids()
702 for i=1,#tree_ids do
703 local tree_id = tree_ids[i]
704 local tree = ltool.trees[tree_id]
705 if(tree.author == playername) then
706 colorstring = "#FFFF00"
707 else
708 colorstring = ""
710 string = string .. colorstring .. tostring(tree_id) .. ": " .. minetest.formspec_escape(tree.name)
711 if(i~=#tree_ids) then
712 string = string .. ","
715 return string, tree_ids
718 --[=[ Here come functions which show formspecs to players ]=]
720 --[[ Shows the main tree form to the given player, starting with the "Edit" tab ]]
721 function ltool.show_treeform(playername)
722 local privs = minetest.get_player_privs(playername)
723 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
724 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
727 --[[ spawns a simple dialog formspec to a player ]]
728 function ltool.show_dialog(playername, formname, message)
729 local formspec = "size[12,2;]label[0,0.2;"..message.."]"..
730 "button[4.5,1.5;3,1;okay;OK]"
731 minetest.show_formspec(playername, formname, formspec)
736 --[=[ End of formspec-relatec functions ]=]
738 --[[ This function does a lot of parameter checks and returns (tree, tree_name) on success.
739 If ANY parameter check fails, the whole function fails.
740 On failure, it returns (nil, <error message string>).]]
741 function ltool.evaluate_edit_fields(fields, ignore_name)
742 local treedef = {}
743 -- Validation helper: Checks for invalid characters for the fields “axiom” and the 4 rule sets
744 local v = function(str)
745 local match = string.match(str, "[^][abcdfABCDFGTR+-/*&^]")
746 if(match==nil) then
747 return true
748 else
749 return false
752 -- Validation helper: Checks for balanced brackets
753 local b = function(str)
754 local brackets = 0
755 for c=1, string.len(str) do
756 local char = string.sub(str, c, c)
757 if char == "[" then
758 brackets = brackets + 1
759 elseif char == "]" then
760 brackets = brackets - 1
761 if brackets < 0 then
762 return false
766 return brackets == 0
769 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
770 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
771 treedef.rules_a = fields.rules_a
772 treedef.rules_b = fields.rules_b
773 treedef.rules_c = fields.rules_c
774 treedef.rules_d = fields.rules_d
775 treedef.axiom = fields.axiom
776 else
777 return nil, "The brackets are unbalanced! For each of the axiom and the rule sets, each opening bracket must be matched by a closing bracket."
779 else
780 return nil, "The axiom or one of the rule sets contains at least one invalid character.\nSee the cheat sheet for a list of allowed characters."
782 treedef.trunk = fields.trunk
783 treedef.leaves = fields.leaves
784 treedef.leaves2 = fields.leaves2
785 treedef.leaves2_chance = fields.leaves2_chance
786 treedef.angle = tonumber(fields.angle)
787 if(treedef.angle == nil) then
788 return nil, "The field \"Angle\" must contain a number."
790 treedef.iterations = tonumber(fields.iterations)
791 if(treedef.iterations == nil) then
792 return nil, "The field \"Iterations\" must contain a natural number greater or equal to 0."
793 elseif(treedef.iterations < 0) then
794 return nil, "The field \"Iterations\" must contain a natural number greater or equal to 0."
796 treedef.random_level = tonumber(fields.random_level)
797 if(treedef.random_level == nil) then
798 return nil, "The field \"Randomness level\" must contain a number."
800 treedef.fruit = fields.fruit
801 treedef.fruit_chance = tonumber(fields.fruit_chance)
802 if(treedef.fruit_chance == nil) then
803 return nil, "The field \"Fruit chance\" must contain a number."
804 elseif(treedef.fruit_chance > 100 or treedef.fruit_chance < 0) then
805 return nil, "Fruit chance must be between 0% and 100%."
807 if(fields.trunk_type == "single" or fields.trunk_type == "double" or fields.trunk_type == "crossed") then
808 treedef.trunk_type = fields.trunk_type
809 else
810 return nil, "Trunk type must be \"single\", \"double\" or \"crossed\"."
812 treedef.thin_branches = fields.thin_branches
813 if(fields.thin_branches == "true") then
814 treedef.thin_branches = true
815 elseif(fields.thin_branches == "false") then
816 treedef.thin_branches = false
817 else
818 return nil, "Field \"Thin branches?\" must be \"true\" or \"false\"."
820 local name = fields.name
821 if(ignore_name ~= true and name == "") then
822 return nil, "Name is empty."
824 return treedef, name
828 --[=[ Here come several utility functions ]=]
830 --[[ converts a given tree to field names, as if they were given to a
831 minetest.register_on_plyer_receive_fields callback function ]]
832 function ltool.tree_to_fields(tree)
833 local s = function(i)
834 if(i==nil) then
835 return ""
836 else
837 return tostring(i)
840 local fields = {}
841 fields.axiom = s(tree.treedef.axiom)
842 fields.rules_a = s(tree.treedef.rules_a)
843 fields.rules_b = s(tree.treedef.rules_b)
844 fields.rules_c = s(tree.treedef.rules_c)
845 fields.rules_d = s(tree.treedef.rules_d)
846 fields.trunk = s(tree.treedef.trunk)
847 fields.leaves = s(tree.treedef.leaves)
848 fields.leaves2 = s(tree.treedef.leaves2)
849 fields.leaves2_chance = s(tree.treedef.leaves2)
850 fields.fruit = s(tree.treedef.fruit)
851 fields.fruit_chance = s(tree.treedef.fruit_chance)
852 fields.angle = s(tree.treedef.angle)
853 fields.iterations = s(tree.treedef.iterations)
854 fields.random_level = s(tree.treedef.random_level)
855 fields.trunk_type = s(tree.treedef.trunk_type)
856 fields.thin_branches = s(tree.treedef.thin_branches)
857 fields.name = s(tree.name)
858 return fields
863 -- returns a simple table of all the tree IDs
864 function ltool.get_tree_ids()
865 local ids = {}
866 for tree_id, _ in pairs(ltool.trees) do
867 table.insert(ids, tree_id)
869 table.sort(ids)
870 return ids
873 --[[ In a table of tree IDs (returned by ltool.get_tree_ids, parameter tree_ids), this function
874 searches for the first occourance of the value searched_tree_id and returns its index.
875 This is basically a reverse lookup utility. ]]
876 function ltool.get_tree_id_index(searched_tree_id, tree_ids)
877 for i=1, #tree_ids do
878 local table_tree_id = tree_ids[i]
879 if(searched_tree_id == table_tree_id) then
880 return i
885 -- Returns the selected tree of the given player
886 function ltool.get_selected_tree(playername)
887 local sel = ltool.playerinfos[playername].dbsel
888 if(sel ~= nil) then
889 local tree_id = ltool.playerinfos[playername].treeform.database.textlist[sel]
890 if(tree_id ~= nil) then
891 return ltool.trees[tree_id]
894 return nil
897 -- Returns the ID of the selected tree of the given player
898 function ltool.get_selected_tree_id(playername)
899 local sel = ltool.playerinfos[playername].dbsel
900 if(sel ~= nil) then
901 return ltool.playerinfos[playername].treeform.database.textlist[sel]
903 return nil
907 ltool.treeform = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit()
909 minetest.register_chatcommand("treeform",
911 params = "",
912 description = "Open L-System Tree Utility.",
913 privs = {},
914 func = function(playername, param)
915 ltool.show_treeform(playername)
919 minetest.register_chatcommand("lplant",
921 description = "Plant a L-system tree at the specified position",
922 privs = { lplant = true },
923 params = "<tree ID> <x> <y> <z> [<seed>]",
924 func = function(playername, param)
925 local p = {}
926 local tree_id, x, y, z, seed = string.match(param, "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *([%d.-]*)")
927 tree_id, p.x, p.y, p.z, seed = tonumber(tree_id), tonumber(x), tonumber(y), tonumber(z), tonumber(seed)
928 if not tree_id or not p.x or not p.y or not p.z then
929 return false, "Invalid usage, see /help lplant."
931 local lm = tonumber(minetest.settings:get("map_generation_limit") or 31000)
932 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
933 return false, "Cannot plant tree out of map bounds!"
936 local success = ltool.plant_tree(tree_id, p, seed)
937 if success == false then
938 return false, "Unknown tree ID!"
939 else
940 return true
945 function ltool.dbsel_to_tree(dbsel, playername)
946 return ltool.trees[ltool.playerinfos[playername].treeform.database.textlist[dbsel]]
949 function ltool.save_fields(playername,formname,fields)
950 if not fields.thin_branches then
951 fields.thin_branches = ltool.playerinfos[playername].treeform.edit.thin_branches
953 if(formname=="ltool:treeform_edit") then
954 ltool.playerinfos[playername].treeform.edit.fields = fields
955 elseif(formname=="ltool:treeform_database") then
956 ltool.playerinfos[playername].treeform.database.fields = fields
957 elseif(formname=="ltool:treeform_plant") then
958 ltool.playerinfos[playername].treeform.plant.fields = fields
962 local function handle_sapling_button_database_plant(seltree, seltree_id, privs, formname, fields, playername)
963 if(seltree ~= nil) then
964 if(privs.lplant ~= true) then
965 ltool.save_fields(playername, formname, fields)
966 local message = "You can't request saplings, you need to have the \"lplant\" privilege."
967 ltool.show_dialog(playername, "ltool:treeform_error_sapling", message)
968 return false
970 local seed = nil
971 if(tonumber(fields.seed)~=nil) then
972 seed = tonumber(fields.seed)
974 if ltool.trees[seltree_id] then
975 local ret, ret2 = ltool.give_sapling(ltool.trees[seltree_id].treedef, seed, playername, true, ltool.trees[seltree_id].name)
976 if(ret==false and ret2==2) then
977 ltool.save_fields(playername, formname, fields)
978 ltool.show_dialog(playername, "ltool:treeform_error_sapling", "Error: The sapling could not be given to you. Probably your inventory is full.")
984 --[=[ Callback functions start here ]=]
985 function ltool.process_form(player,formname,fields)
986 local playername = player:get_player_name()
988 local seltree = ltool.get_selected_tree(playername)
989 local seltree_id = ltool.get_selected_tree_id(playername)
990 local privs = minetest.get_player_privs(playername)
991 local s = function(input)
992 local ret
993 if(input==nil) then
994 ret = ""
995 else
996 ret = minetest.formspec_escape(tostring(input))
998 return ret
1000 -- Update thin_branches field
1001 if(formname == "ltool:treeform_edit") then
1002 if(not fields.thin_branches) then
1003 fields.thin_branches = ltool.playerinfos[playername].treeform.edit.thin_branches
1004 if(not fields.thin_branches) then
1005 minetest.log("error", "[ltool] thin_branches field of "..playername.." is nil!")
1007 else
1008 ltool.playerinfos[playername].treeform.edit.thin_branches = fields.thin_branches
1011 --[[ process clicks on the tab header ]]
1012 if(formname == "ltool:treeform_edit" or formname == "ltool:treeform_database" or formname == "ltool:treeform_plant" or formname == "ltool:treeform_help") then
1013 if fields.ltool_tab ~= nil then
1014 ltool.save_fields(playername, formname, fields)
1015 local tab = tonumber(fields.ltool_tab)
1016 local formspec, subformname, contents
1017 if(tab==1) then
1018 contents = ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
1019 subformname = "edit"
1020 elseif(tab==2) then
1021 contents = ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1022 subformname = "database"
1023 elseif(tab==3) then
1024 if(ltool.number_of_trees > 0) then
1025 contents = ltool.tab_plant(seltree, ltool.playerinfos[playername].treeform.plant.fields, privs.lplant)
1026 else
1027 contents = ltool.tab_plant(nil, nil, privs.lplant)
1029 subformname = "plant"
1030 elseif(tab==4) then
1031 contents = ltool.tab_help(ltool.playerinfos[playername].treeform.help.tab)
1032 subformname = "help"
1034 formspec = ltool.formspec_size..ltool.formspec_header(tab)..contents
1035 minetest.show_formspec(playername, "ltool:treeform_" .. subformname, formspec)
1036 return
1039 --[[ "Plant" tab ]]
1040 if(formname == "ltool:treeform_plant") then
1041 if(fields.plant_plant) then
1042 if(seltree ~= nil) then
1043 if(privs.lplant ~= true) then
1044 ltool.save_fields(playername, formname, fields)
1045 local message = "You can't plant trees, you need to have the \"lplant\" privilege."
1046 ltool.show_dialog(playername, "ltool:treeform_error_lplant", message)
1047 return
1049 minetest.log("action","[ltool] Planting tree")
1050 local treedef = seltree.treedef
1052 local x,y,z = tonumber(fields.x), tonumber(fields.y), tonumber(fields.z)
1053 local distance = tonumber(fields.distance)
1054 local tree_pos
1055 local fail_coordinates = function()
1056 ltool.save_fields(playername, formname, fields)
1057 ltool.show_dialog(playername, "ltool:treeform_error_badplantfields", "Error: When using coordinates, you have to specifiy numbers in the fields \"x\", \"y\", \"z\".")
1059 local fail_distance = function()
1060 ltool.save_fields(playername, formname, fields)
1061 ltool.show_dialog(playername, "ltool:treeform_error_badplantfields", "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\".")
1063 if(fields.plantmode == "Absolute coordinates") then
1064 if(type(x)~="number" or type(y) ~= "number" or type(z) ~= "number") then
1065 fail_coordinates()
1066 return
1068 tree_pos = {x=x, y=y, z=z}
1069 elseif(fields.plantmode == "Relative coordinates") then
1070 if(type(x)~="number" or type(y) ~= "number" or type(z) ~= "number") then
1071 fail_coordinates()
1072 return
1074 tree_pos = player:getpos()
1075 tree_pos.x = tree_pos.x + x
1076 tree_pos.y = tree_pos.y + y
1077 tree_pos.z = tree_pos.z + z
1078 elseif(fields.plantmode == "Distance in viewing direction") then
1079 if(type(distance)~="number") then
1080 fail_distance()
1081 return
1083 tree_pos = vector.round(vector.add(player:getpos(), vector.multiply(player:get_look_dir(), distance)))
1084 else
1085 minetest.log("error", "[ltool] fields.plantmode = "..tostring(fields.plantmode))
1088 if(tonumber(fields.seed)~=nil) then
1089 treedef.seed = tonumber(fields.seed)
1092 ltool.plant_tree(seltree_id, tree_pos)
1094 treedef.seed = nil
1096 elseif(fields.sapling) then
1097 local ret = handle_sapling_button_database_plant(seltree, seltree_id, privs, formname, fields, playername)
1098 if ret == false then
1099 return
1102 --[[ "Edit" tab ]]
1103 elseif(formname == "ltool:treeform_edit") then
1104 if(fields.edit_save or fields.edit_sapling) then
1105 local param1, param2
1106 param1, param2 = ltool.evaluate_edit_fields(fields, fields.edit_sapling ~= nil)
1107 if(fields.edit_save and privs.ledit ~= true) then
1108 ltool.save_fields(playername, formname, fields)
1109 local message = "You can't save trees, you need to have the \"ledit\" privilege."
1110 ltool.show_dialog(playername, "ltool:treeform_error_ledit", message)
1111 return
1113 if(fields.edit_sapling and privs.lplant ~= true) then
1114 ltool.save_fields(playername, formname, fields)
1115 local message = "You can't request saplings, you need to have the \"lplant\" privilege."
1116 ltool.show_dialog(playername, "ltool:treeform_error_ledit", message)
1117 return
1119 local tree_ok = true
1120 local treedef, name
1121 if(param1 ~= nil) then
1122 treedef = param1
1123 name = param2
1124 for k,v in pairs(ltool.trees) do
1125 if(fields.edit_save and v.name == name) then
1126 ltool.save_fields(playername, formname, fields)
1127 if(v.author == playername) then
1128 local formspec = "size[6,2;]label[0,0.2;You already have a tree with this name.\nDo you want to replace it?]"..
1129 "button[0,1.5;3,1;replace_yes;Yes]"..
1130 "button[3,1.5;3,1;replace_no;No]"
1131 minetest.show_formspec(playername, "ltool:treeform_replace", formspec)
1132 else
1133 ltool.show_dialog(playername, "ltool:treeform_error_nameclash", "Error: This name is already taken by someone else.\nPlease choose a different name.")
1135 return
1138 else
1139 tree_ok = false
1141 ltool.save_fields(playername, formname, fields)
1142 if(tree_ok == true) then
1143 if fields.edit_save then
1144 ltool.add_tree(name, playername, treedef)
1145 elseif fields.edit_sapling then
1146 local ret, ret2 = ltool.give_sapling(treedef, tostring(ltool.seed), playername, true, fields.name)
1147 if(ret==false and ret2==2) then
1148 ltool.save_fields(playername, formname, fields)
1149 ltool.show_dialog(playername, "ltool:treeform_error_sapling", "Error: The sapling could not be given to you. Probably your inventory is full.")
1152 else
1153 local message = "Error: The tree definition is invalid.\n"..
1154 minetest.formspec_escape(param2)
1155 ltool.show_dialog(playername, "ltool:treeform_error_badtreedef", message)
1158 if(fields.edit_clear) then
1159 local privs = minetest.get_player_privs(playername)
1160 ltool.save_fields(playername, formname, ltool.default_edit_fields)
1161 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.default_edit_fields, privs.ledit, privs.lplant)
1163 --[[ hacky_spaces is part of a workaround, see comment on hacky_spaces in ltool.join.
1164 This workaround will slightly change the formspec by adding 0-5 spaces
1165 to the end, changing the number of spaces on each send. This forces
1166 Minetest to re-send the formspec.
1167 Spaces are completely harmless in a formspec.]]
1168 -- BEGIN OF WORKAROUND
1169 local hacky_spaces = ltool.playerinfos[playername].treeform.hacky_spaces
1170 hacky_spaces = hacky_spaces .. " "
1171 if string.len(hacky_spaces) > 5 then
1172 hacky_spaces = ""
1174 ltool.playerinfos[playername].treeform.hacky_spaces = hacky_spaces
1175 local real_formspec = formspec .. hacky_spaces
1176 -- END OF WORKAROUND
1178 minetest.show_formspec(playername, "ltool:treeform_edit", real_formspec)
1180 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
1181 local fragment
1182 if(fields.edit_axiom) then
1183 fragment = "axiom;Axiom;"..s(fields.axiom)
1184 elseif(fields.edit_rules_a) then
1185 fragment = "rules_a;Rules set A;"..s(fields.rules_a)
1186 elseif(fields.edit_rules_b) then
1187 fragment = "rules_b;Rules set B;"..s(fields.rules_b)
1188 elseif(fields.edit_rules_c) then
1189 fragment = "rules_c;Rules set C;"..s(fields.rules_c)
1190 elseif(fields.edit_rules_d) then
1191 fragment = "rules_d;Rules set D;"..s(fields.rules_d)
1194 ltool.save_fields(playername, formname, fields)
1195 local formspec = ltool.formspec_editplus(fragment)
1196 minetest.show_formspec(playername, "ltool:treeform_editplus", formspec)
1198 if(mod_select_item and (fields.edit_trunk or fields.edit_leaves or fields.edit_leaves2 or fields.edit_fruit)) then
1199 ltool.save_fields(playername, formname, fields)
1200 -- Prepare sorting.
1201 -- Move tree, leaves, apple/leafdecay nodes to the beginning
1202 local compare_group, fruit
1203 if fields.edit_trunk then
1204 compare_group = "tree"
1205 elseif fields.edit_leaves or fields.edit_leaves2 then
1206 compare_group = "leaves"
1207 elseif fields.edit_fruit or fields.edit_fruit then
1208 compare_group = "leafdecay"
1209 local alias = minetest.registered_aliases["mapgen_apple"]
1210 if alias and minetest.registered_nodes[alias] then
1211 fruit = alias
1214 select_item.show_dialog(playername, "ltool:node", function(itemstring)
1215 if itemstring ~= "air" and minetest.registered_nodes[itemstring] ~= nil then
1216 return true
1218 end,
1219 function(i1, i2)
1220 if fruit and i1 == fruit then
1221 return true
1223 if fruit and i2 == fruit then
1224 return false
1226 local i1t = minetest.get_item_group(i1, compare_group)
1227 local i2t = minetest.get_item_group(i2, compare_group)
1228 local i1d = minetest.registered_items[i1].description
1229 local i2d = minetest.registered_items[i2].description
1230 local i1nici = minetest.get_item_group(i1, "not_in_creative_inventory")
1231 local i2nici = minetest.get_item_group(i2, "not_in_creative_inventory")
1232 if (i1d == "" and i2d ~= "") then
1233 return false
1234 elseif (i1d ~= "" and i2d == "") then
1235 return true
1237 if (i1nici == 1 and i2nici == 0) then
1238 return false
1239 elseif (i1nici == 0 and i2nici == 1) then
1240 return true
1242 if i1t < i2t then
1243 return false
1244 elseif i1t > i2t then
1245 return true
1247 return i1 < i2
1248 end)
1250 --[[ Larger edit fields for axiom and rules fields ]]
1251 elseif(formname == "ltool:treeform_editplus") then
1252 local editfields = ltool.playerinfos[playername].treeform.edit.fields
1253 local function addchar(c)
1254 local fragment
1255 if(c=="P") then c = "[" end
1256 if(c=="p") then c = "]" end
1257 if(fields.axiom) then
1258 fragment = "axiom;Axiom;"..s(fields.axiom..c)
1259 elseif(fields.rules_a) then
1260 fragment = "rules_a;Rules set A;"..s(fields.rules_a..c)
1261 elseif(fields.rules_b) then
1262 fragment = "rules_b;Rules set B;"..s(fields.rules_b..c)
1263 elseif(fields.rules_c) then
1264 fragment = "rules_c;Rules set C;"..s(fields.rules_c..c)
1265 elseif(fields.rules_d) then
1266 fragment = "rules_d;Rules set D;"..s(fields.rules_d..c)
1268 local formspec = ltool.formspec_editplus(fragment)
1269 minetest.show_formspec(playername, "ltool:treeform_editplus", formspec)
1271 if(fields.editplus_save) then
1272 local function o(writed, writer)
1273 if(writer~=nil) then
1274 return writer
1275 else
1276 return writed
1279 editfields.axiom = o(editfields.axiom, fields.axiom)
1280 editfields.rules_a = o(editfields.rules_a, fields.rules_a)
1281 editfields.rules_b = o(editfields.rules_b, fields.rules_b)
1282 editfields.rules_c = o(editfields.rules_c, fields.rules_c)
1283 editfields.rules_d = o(editfields.rules_d, fields.rules_d)
1284 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1285 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1286 elseif(fields.editplus_cancel or fields.quit) then
1287 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1288 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1289 else
1290 for id, field in pairs(fields) do
1291 if(string.sub(id,1,11) == "editplus_c_") then
1292 local char = string.sub(id,12,12)
1293 addchar(char)
1297 --[[ "Database" tab ]]
1298 elseif(formname == "ltool:treeform_database") then
1299 if(fields.treelist) then
1300 local event = minetest.explode_textlist_event(fields.treelist)
1301 if(event.type == "CHG") then
1302 ltool.playerinfos[playername].dbsel = event.index
1303 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(event.index, playername)
1304 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1306 elseif(fields.database_copy) then
1307 if(seltree ~= nil) then
1308 if(ltool.playerinfos[playername] ~= nil) then
1309 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.tree_to_fields(seltree), privs.ledit, privs.lplant)
1310 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1312 else
1313 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", "Error: No tree is selected.")
1315 elseif(fields.database_update) then
1316 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1317 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1319 elseif(fields.database_delete) then
1320 if(privs.ledit ~= true) then
1321 ltool.save_fields(playername, formname, fields)
1322 local message = "You can't delete trees, you need to have the \"ledit\" privilege."
1323 ltool.show_dialog(playername, "ltool:treeform_error_ledit_db", message)
1324 return
1326 if(seltree ~= nil) then
1327 if(playername == seltree.author) then
1328 local remove_id = ltool.get_selected_tree_id(playername)
1329 if(remove_id ~= nil) then
1330 ltool.remove_tree(remove_id)
1331 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1332 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1334 else
1335 ltool.show_dialog(playername, "ltool:treeform_error_delete", "Error: This tree is not your own. You may only delete your own trees.")
1337 else
1338 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", "Error: No tree is selected.")
1340 elseif(fields.database_rename) then
1341 if(seltree ~= nil) then
1342 if(privs.ledit ~= true) then
1343 ltool.save_fields(playername, formname, fields)
1344 local message = "You can't rename trees, you need to have the \"ledit\" privilege."
1345 ltool.show_dialog(playername, "ltool:treeform_error_ledit_db", message)
1346 return
1348 if(playername == seltree.author) then
1349 local formspec = "field[newname;New name:;"..minetest.formspec_escape(seltree.name).."]"
1350 minetest.show_formspec(playername, "ltool:treeform_rename", formspec)
1351 else
1352 ltool.show_dialog(playername, "ltool:treeform_error_rename_forbidden", "Error: This tree is not your own. You may only rename your own trees.")
1354 else
1355 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", "Error: No tree is selected.")
1357 elseif(fields.sapling) then
1358 local ret = handle_sapling_button_database_plant(seltree, seltree_id, privs, formname, fields, playername)
1359 if ret == false then
1360 return
1363 --[[ Process "Do you want to replace this tree?" dialog ]]
1364 elseif(formname == "ltool:treeform_replace") then
1365 local editfields = ltool.playerinfos[playername].treeform.edit.fields
1366 local newtreedef, newname = ltool.evaluate_edit_fields(editfields)
1367 if(privs.ledit ~= true) then
1368 local message = "You can't overwrite trees, you need to have the \"ledit\" privilege."
1369 minetest.show_dialog(playername, "ltool:treeform_error_ledit", message)
1370 return
1372 if(fields.replace_yes) then
1373 for tree_id,tree in pairs(ltool.trees) do
1374 if(tree.name == newname) then
1375 --[[ The old tree is deleted and a
1376 new one with a new ID is created ]]
1377 local new_tree_id = ltool.next_tree_id
1378 ltool.trees[new_tree_id] = {}
1379 ltool.trees[new_tree_id].treedef = newtreedef
1380 ltool.trees[new_tree_id].name = newname
1381 ltool.trees[new_tree_id].author = tree.author
1382 ltool.next_tree_id = ltool.next_tree_id + 1
1383 ltool.trees[tree_id] = nil
1384 ltool.playerinfos[playername].dbsel = ltool.number_of_trees
1388 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1389 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1390 elseif(formname == "ltool:treeform_help") then
1391 local tab = tonumber(fields.ltool_help_tab)
1392 if(tab ~= nil) then
1393 ltool.playerinfos[playername].treeform.help.tab = tab
1394 local formspec = ltool.formspec_size..ltool.formspec_header(4)..ltool.tab_help(tab)
1395 minetest.show_formspec(playername, "ltool:treeform_help", formspec)
1397 if(fields.create_template) then
1398 local newfields = {
1399 axiom="FFFFFAFFBF",
1400 rules_a="[&&&FFFFF&&FFFF][&&&++++FFFFF&&FFFF][&&&----FFFFF&&FFFF]",
1401 rules_b="[&&&++FFFFF&&FFFF][&&&--FFFFF&&FFFF][&&&------FFFFF&&FFFF]",
1402 trunk="mapgen_tree",
1403 leaves="mapgen_leaves",
1404 leaves2_chance="0",
1405 angle="30",
1406 iterations="2",
1407 random_level="0",
1408 trunk_type="single",
1409 thin_branches="true",
1410 fruit_chance="10",
1411 fruit="mapgen_apple",
1412 name = "Example Tree "..ltool.next_tree_id
1414 ltool.save_fields(playername, formname, newfields)
1415 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(newfields, privs.ledit, privs.lplant)
1416 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1418 --[[ Tree renaming dialog ]]
1419 elseif(formname == "ltool:treeform_rename") then
1420 if(privs.ledit ~= true) then
1421 ltool.save_fields(playername, formname, fields)
1422 local message = "You can't delete trees, you need to have the \"ledit\" privilege."
1423 ltool.show_dialog(playername, "ltool:treeform_error_ledit_delete", message)
1424 return
1426 if(fields.newname ~= "" and fields.newname ~= nil) then
1427 ltool.rename_tree(ltool.get_selected_tree_id(playername), fields.newname)
1428 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1429 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1430 else
1431 ltool.show_dialog(playername, "ltool:treeform_error_bad_rename", "Error: This name is empty. The tree name must be non-empty.")
1433 --[[ Here come various error messages to handle ]]
1434 elseif(formname == "ltool:treeform_error_badtreedef" or formname == "ltool:treeform_error_nameclash" or formname == "ltool:treeform_error_ledit") then
1435 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
1436 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1437 elseif(formname == "ltool:treeform_error_badplantfields" or formname == "ltool:treeform_error_sapling" or formname == "ltool:treeform_error_lplant") then
1438 local formspec = ltool.formspec_size..ltool.formspec_header(3)..ltool.tab_plant(seltree, ltool.playerinfos[playername].treeform.plant.fields, privs.lplant)
1439 minetest.show_formspec(playername, "ltool:treeform_plant", formspec)
1440 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
1441 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1442 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1443 elseif(formname == "ltool:treeform_error_bad_rename") then
1444 local formspec = "field[newname;New name:;"..minetest.formspec_escape(seltree.name).."]"
1445 minetest.show_formspec(playername, "ltool:treeform_rename", formspec)
1446 else
1447 -- Action for Inventory++ button
1448 if fields.ltool and minetest.get_modpath("inventory_plus") then
1449 ltool.show_treeform(playername)
1450 return
1455 if mod_select_item then
1456 select_item.register_on_select_item(function(playername, dialogname, itemstring)
1457 if dialogname == "ltool:node" then
1458 if itemstring then
1459 local f = ltool.playerinfos[playername].treeform.edit.fields
1460 if f.edit_trunk then
1461 f.trunk = itemstring
1462 elseif f.edit_leaves then
1463 f.leaves = itemstring
1464 elseif f.edit_leaves2 then
1465 f.leaves2 = itemstring
1466 elseif f.edit_fruit then
1467 f.fruit = itemstring
1470 ltool.show_treeform(playername)
1471 return false
1473 end)
1476 --[[ These 2 functions are basically just table initializions and cleanups ]]
1477 function ltool.leave(player)
1478 ltool.playerinfos[player:get_player_name()] = nil
1481 function ltool.join(player)
1482 local infotable = {}
1483 infotable.dbsel = nil
1484 infotable.treeform = {}
1485 infotable.treeform.database = {}
1486 --[[ This table stores a mapping of the textlist IDs in the database formspec and the tree IDs.
1487 It is updated each time ltool.tab_database is called. ]]
1488 infotable.treeform.database.textlist = {}
1489 --[[ the “fields” tables store the values of the input fields of a formspec. It is updated
1490 whenever the formspec is changed, i.e. on tab change ]]
1491 infotable.treeform.database.fields = {}
1492 infotable.treeform.plant = {}
1493 infotable.treeform.plant.fields = {}
1494 infotable.treeform.edit = {}
1495 infotable.treeform.edit.fields = ltool.default_edit_fields
1496 infotable.treeform.edit.thin_branches = "true"
1497 infotable.treeform.help = {}
1498 infotable.treeform.help.tab = 1
1499 --[[ Workaround for annoying bug in Minetest: When you call the identical formspec twice,
1500 Minetest does not send the second one. This is an issue when the player has changed the
1501 input fields in the meanwhile, resetting fields will fail sometimes.
1502 TODO: Remove workaround when not needed anymore. ]]
1503 -- BEGIN OF WORKAROUND
1504 infotable.treeform.hacky_spaces = ""
1505 -- END OF WORKAROUND
1507 ltool.playerinfos[player:get_player_name()] = infotable
1509 -- Add Inventory++ support
1510 if minetest.get_modpath("inventory_plus") then
1511 inventory_plus.register_button(player, "ltool", "L-System Tree Utility")
1515 function ltool.save_to_file()
1516 local savetable = {}
1517 savetable.trees = ltool.trees
1518 savetable.number_of_trees = ltool.number_of_trees
1519 savetable.next_tree_id = ltool.next_tree_id
1520 local savestring = minetest.serialize(savetable)
1521 local filepath = minetest.get_worldpath().."/ltool.mt"
1522 local file = io.open(filepath, "w")
1523 if(file) then
1524 file:write(savestring)
1525 io.close(file)
1526 minetest.log("action", "[ltool] Tree data saved to "..filepath..".")
1527 else
1528 minetest.log("error", "[ltool] Failed to write ltool data to "..filepath".")
1533 minetest.register_on_player_receive_fields(ltool.process_form)
1535 minetest.register_on_leaveplayer(ltool.leave)
1537 minetest.register_on_joinplayer(ltool.join)
1539 minetest.register_on_shutdown(ltool.save_to_file)
1541 local button_action = function(player)
1542 ltool.show_treeform(player:get_player_name())
1545 if minetest.get_modpath("unified_inventory") ~= nil then
1546 unified_inventory.register_button("ltool", {
1547 type = "image",
1548 image = "ltool_sapling.png",
1549 tooltip = "L-System Tree Utility",
1550 action = button_action,
1554 if minetest.get_modpath("sfinv_buttons") ~= nil then
1555 sfinv_buttons.register_button("ltool", {
1556 title = "L-System Tree Utility",
1557 tooltip = "Invent your own trees and plant them",
1558 image = "ltool_sapling.png",
1559 action = button_action,