Fix incorrect initialization of database.textlist
[minetest_ltool.git] / init.lua
blob3e6e3b57d7a967b64a4b7fe666cd8abdfb4ca15c
1 ltool = {}
3 ltool.VERSION = {}
4 ltool.VERSION.MAJOR = 1
5 ltool.VERSION.MINOR = 4
6 ltool.VERSION.PATCH = 1
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="",
17 leaves="",
18 leaves2="",
19 leaves2_chance="",
20 fruit="",
21 fruit_chance="",
22 angle="",
23 iterations="",
24 random_level="",
25 trunk_type="",
26 thin_branches="",
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 --[[ Register privileges ]]
99 minetest.register_privilege("ledit", {
100 description = "Can add, edit, rename and delete own L-system tree definitions of the ltool mod",
101 give_to_singleplayer = false,
103 minetest.register_privilege("lplant", {
104 description = "Can place L-system trees and get L-system tree samplings of the ltool mod",
105 give_to_singleplayer = false,
108 --[[ Load previously saved data from file or initialize an empty tree table ]]
110 local filepath = minetest.get_worldpath().."/ltool.mt"
111 local file = io.open(filepath, "r")
112 if(file) then
113 local string = file:read()
114 io.close(file)
115 if(string ~= nil) then
116 local savetable = minetest.deserialize(string)
117 if(savetable ~= nil) then
118 ltool.trees = savetable.trees
119 ltool.next_tree_id = savetable.next_tree_id
120 ltool.number_of_trees = savetable.number_of_trees
121 minetest.log("action", "[ltool] Tree data loaded from "..filepath..".")
122 else
123 minetest.log("error", "[ltool] Failed to load tree data from "..filepath..".")
125 else
126 minetest.log("error", "[ltool] Failed to load tree data from "..filepath..".")
128 else
129 --[[ table of all trees ]]
130 ltool.trees = {}
131 --[[ helper variables to ensure unique IDs ]]
132 ltool.number_of_trees = 0
133 ltool.next_tree_id = 1
137 --[[ Adds a tree to the tree table.
138 name: The tree’s name.
139 author: The author’s / owners’ name
140 treedef: The full tree definition, see lua_api.txt
142 returns the tree ID of the new tree
144 function ltool.add_tree(name, author, treedef)
145 local id = ltool.next_tree_id
146 ltool.trees[id] = {name = name, author = author, treedef = treedef}
147 ltool.next_tree_id = ltool.next_tree_id + 1
148 ltool.number_of_trees = ltool.number_of_trees + 1
149 return id
152 --[[ Removes a tree from the database
153 tree_id: ID of the tree to be removed
155 returns nil
157 function ltool.remove_tree(tree_id)
158 ltool.trees[tree_id] = nil
159 ltool.number_of_trees = ltool.number_of_trees - 1
160 for k,v in pairs(ltool.playerinfos) do
161 if(v.dbsel ~= nil) then
162 if(v.dbsel > ltool.number_of_trees) then
163 v.dbsel = ltool.number_of_trees
165 if(v.dbsel < 1) then
166 v.dbsel = 1
172 --[[ Renames a tree in the database
173 tree_id: ID of the tree to be renamed
174 new_name: The name of the tree
176 returns nil
178 function ltool.rename_tree(tree_id, new_name)
179 ltool.trees[tree_id].name = new_name
182 --[[ Copies a tree in the database
183 tree_id: ID of the tree to be copied
185 returns: the ID of the copy on success;
186 false on failure (tree does not exist)
188 function ltool.copy_tree(tree_id)
189 local tree = ltool.trees[tree_id]
190 if(tree == nil) then
191 return false
193 return ltool.add_tree(tree.name, tree.author, tree.treedef)
196 --[[ Gives a L-system tree sapling to a player
197 treedef: L-system tree definition table of tree the sapling will grow
198 seed: Seed of the tree (optional; can be nil)
199 playername: name of the player to which
200 ignore_priv: if true, player’s lplant privilige is not checked (optional argument; default: false)
201 treename: Descriptive name of the tree for the item description (optional, is ignored if nil or empty string)
203 returns:
204 true on success
205 false, 1 if privilege is not sufficient
206 false, 2 if player’s inventory is full
208 function ltool.give_sapling(treedef, seed, player_name, ignore_priv, treename)
209 local privs = minetest.get_player_privs(player_name)
210 if(ignore_priv == nil) then ignore_priv = false end
211 if(ignore_priv == false and privs.lplant ~= true) then
212 return false, 1
215 local sapling = ItemStack("ltool:sapling")
216 local player = minetest.get_player_by_name(player_name)
217 treedef.seed = seed
218 local smeta = sapling:get_meta()
219 smeta:set_string("treedef", minetest.serialize(treedef))
220 if treename and treename ~= "" then
221 smeta:set_string("description", string.format(sapling_format_string, treename))
223 treedef.seed = nil
224 local leftover = player:get_inventory():add_item("main", sapling)
225 if(not leftover:is_empty()) then
226 return false, 2
227 else
228 return true
232 --[[ Plants a tree as the specified position
233 tree_id: ID of tree to be planted
234 pos: Position of tree, in format {x=?, y=?, z=?}
235 seed: Optional seed for randomness, equal seed makes equal trees
237 returns false on failure, nil otherwise
239 function ltool.plant_tree(tree_id, pos, seed)
240 local tree = ltool.trees[tree_id]
241 if(tree==nil) then
242 return false
244 local treedef
245 if seed ~= nil then
246 treedef = table.copy(tree.treedef)
247 treedef.seed = seed
248 else
249 treedef = tree.treedef
251 minetest.spawn_tree(pos, treedef)
254 --[[ Tries to return a tree data structure for a given tree_id
256 tree_id: ID of tee to be returned
258 returns false on failure, a tree otherwise
260 function ltool.get_tree(tree_id)
261 local tree = ltool.trees[tree_id]
262 if(tree==nil) then
263 return false
265 return tree
269 ltool.seed = os.time()
272 --[=[ Here come the functions to build the main formspec.
273 They do not build the entire formspec ]=]
275 ltool.formspec_size = "size[12,9]"
277 --[[ This is a part of the main formspec: Tab header ]]
278 function ltool.formspec_header(index)
279 return "tabheader[0,0;ltool_tab;Edit,Database,Plant,Help;"..tostring(index)..";true;false]"
282 --[[ This creates the edit tab of the formspec
283 fields: A template used to fill the default values of the formspec. ]]
284 function ltool.tab_edit(fields, has_ledit_priv, has_lplant_priv)
285 if(fields==nil) then
286 fields = ltool.default_edit_fields
288 local s = function(input)
289 local ret
290 if(input==nil) then
291 ret = ""
292 else
293 ret = minetest.formspec_escape(tostring(input))
295 return ret
298 -- Show save/clear buttons depending on privs
299 local leditbuttons
300 if has_ledit_priv then
301 leditbuttons = "button[0,8.7;4,0;edit_save;Save tree to database]"..
302 "button[4,8.7;4,0;edit_clear;Clear fields]"
303 if has_lplant_priv then
304 leditbuttons = leditbuttons .. "button[8,8.7;4,0;edit_sapling;Give me a sapling]"
306 else
307 leditbuttons = "label[0,8.3;Read-only mode. You need the “ledit” privilege to save trees to the database.]"
310 local nlength = "3"
311 local fields_select_item = ""
312 if mod_select_item then
313 nlength = "2.6"
314 fields_select_item = ""..
315 "button[2.4,5.7;0.5,0;edit_trunk;>]"..
316 "button[5.4,5.7;0.5,0;edit_leaves;>]"..
317 "button[8.4,5.7;0.5,0;edit_leaves2;>]"..
318 "button[11.4,5.7;0.5,0;edit_fruit;>]"..
319 "tooltip[edit_trunk;Select node]"..
320 "tooltip[edit_leaves;Select node]"..
321 "tooltip[edit_leaves2;Select node]"..
322 "tooltip[edit_fruit;Select node]"
325 return ""..
326 "field[0.2,1;11,0;axiom;Axiom;"..s(fields.axiom).."]"..
327 "button[11,0.7;1,0;edit_axiom;+]"..
328 "tooltip[edit_axiom;Opens larger text field for Axiom]"..
329 "field[0.2,2;11,0;rules_a;Rules set A;"..s(fields.rules_a).."]"..
330 "button[11,1.7;1,0;edit_rules_a;+]"..
331 "tooltip[edit_rules_a;Opens larger text field for Rules set A]"..
332 "field[0.2,3;11,0;rules_b;Rules set B;"..s(fields.rules_b).."]"..
333 "button[11,2.7;1,0;edit_rules_b;+]"..
334 "tooltip[edit_rules_b;Opens larger text field for Rules set B]"..
335 "field[0.2,4;11,0;rules_c;Rules set C;"..s(fields.rules_c).."]"..
336 "button[11,3.7;1,0;edit_rules_c;+]"..
337 "tooltip[edit_rules_c;Opens larger text field for Rules set C]"..
338 "field[0.2,5;11,0;rules_d;Rules set D;"..s(fields.rules_d).."]"..
339 "button[11,4.7;1,0;edit_rules_d;+]"..
340 "tooltip[edit_rules_d;Opens larger text field for Rules set D]"..
342 "field[0.2,6;"..nlength..",0;trunk;Trunk node name;"..s(fields.trunk).."]"..
343 "field[3.2,6;"..nlength..",0;leaves;Leaves node name;"..s(fields.leaves).."]"..
344 "field[6.2,6;"..nlength..",0;leaves2;Secondary leaves node name;"..s(fields.leaves2).."]"..
345 "field[9.2,6;"..nlength..",0;fruit;Fruit node name;"..s(fields.fruit).."]"..
346 fields_select_item..
348 "field[0.2,7;3,0;trunk_type;Trunk type (single/double/crossed);"..s(fields.trunk_type).."]"..
349 "tooltip[trunk_type;This field specifies the shape of the tree trunk. Possible values:\n- \"single\": trunk of size 1×1\n- \"double\": trunk of size 2×2\n- \"crossed\": trunk in cross shape (3×3).]"..
350 "field[3.2,7;3,0;thin_branches;Thin branches? (true/false);"..s(fields.thin_branches).."]"..
351 "tooltip[thin_branches;\"true\": All branches are just 1 node wide. \"false\": Branches can be larger.]"..
352 "field[6.2,7;3,0;leaves2_chance;Secondary leaves chance (%);"..s(fields.leaves2_chance).."]"..
353 "tooltip[leaves2_chance;Chance (in percent) to replace a leaves node by a secondary leaves node]"..
354 "field[9.2,7;3,0;fruit_chance;Fruit chance (%);"..s(fields.fruit_chance).."]"..
355 "tooltip[fruit_chance;Chance (in percent) to replace a leaves node by a fruit node.]"..
357 "field[0.2,8;3,0;iterations;Iterations;"..s(fields.iterations).."]"..
358 "tooltip[iterations;Maximum number of iterations, usually between 2 and 5.]"..
359 "field[3.2,8;3,0;random_level;Randomness level;"..s(fields.random_level).."]"..
360 "tooltip[random_level;Factor to lower number of iterations, usually between 0 and 3.]"..
361 "field[6.2,8;3,0;angle;Angle (in °);"..s(fields.angle).."]"..
362 "field[9.2,8;3,0;name;Name;"..s(fields.name).."]"..
363 "tooltip[name;Descriptive name for this tree, only used for convenience.]"..
364 leditbuttons
367 --[[ This creates the database tab of the formspec.
368 index: Selected index of the textlist
369 playername: To whom the formspec is shown
371 function ltool.tab_database(index, playername)
372 local treestr, tree_ids = ltool.build_tree_textlist(index, playername)
373 if(treestr ~= nil) then
374 local indexstr
375 if(index == nil) then
376 indexstr = ""
377 else
378 indexstr = tostring(index)
380 ltool.playerinfos[playername].treeform.database.textlist = tree_ids
382 local leditbuttons
383 if minetest.get_player_privs(playername).ledit then
384 leditbuttons = "button[3,7.5;3,1;database_rename;Rename tree]"..
385 "button[6,7.5;3,1;database_delete;Delete tree]"
386 else
387 leditbuttons = "label[0.2,7.2;Read-only mode. You need the “ledit” privilege to edit trees.]"
390 return ""..
391 "textlist[0,0;11,7;treelist;"..treestr..";"..tostring(index)..";false]"..
392 leditbuttons..
393 "button[3,8.5;3,1;database_copy;Copy tree to editor]"..
394 "button[6,8.5;3,1;database_update;Reload database]"
395 else
396 return "label[0,0;The tree database is empty.]"..
397 "button[6.5,8.5;3,1;database_update;Reload database]"
401 --[[ This creates the "Plant" tab part of the main formspec ]]
402 function ltool.tab_plant(tree, fields, has_lplant_priv)
403 if(tree ~= nil) then
404 local seltree = "label[0,-0.2;Selected tree: "..minetest.formspec_escape(tree.name).."]"
405 if not has_lplant_priv then
406 return seltree..
407 "label[0,0.3;Planting of trees is not allowed. You need to have the “lplant” privilege.]"
409 if(fields==nil) then
410 fields = {}
412 local s = function(i)
413 if(i==nil) then return ""
414 else return tostring(minetest.formspec_escape(i))
417 local seed
418 if(fields.seed == nil) then
419 seed = tostring(ltool.seed)
420 else
421 seed = fields.seed
423 local dropdownindex
424 if(fields.plantmode == "Absolute coordinates") then
425 dropdownindex = 1
426 elseif(fields.plantmode == "Relative coordinates") then
427 dropdownindex = 2
428 elseif(fields.plantmode == "Distance in viewing direction") then
429 dropdownindex = 3
430 else
431 dropdownindex = 1
434 return ""..
435 seltree..
436 "dropdown[-0.1,0.5;5;plantmode;Absolute coordinates,Relative coordinates,Distance in viewing direction;"..dropdownindex.."]"..
437 --[[ NOTE: This tooltip does not work for the dropdown list in 0.4.10,
438 but it is added anyways in case this gets fixed in later Minetest versions. ]]
439 "tooltip[plantmode;"..
440 "- \"Absolute coordinates\": Fields \"x\", \"y\" and \"z\" specify the absolute world coordinates where to plant the tree\n"..
441 "- \"Relative coordinates\": Fields \"x\", \"y\" and \"z\" specify the relative position from your position\n"..
442 "- \"Distance in viewing direction\": Plant tree relative from your position in the direction you look to, at the specified distance"..
443 "]"..
444 "field[0.2,-2;6,10;x;x;"..s(fields.x).."]"..
445 "tooltip[x;Field is only used by absolute and relative coordinates.]"..
446 "field[0.2,-1;6,10;y;y;"..s(fields.y).."]"..
447 "tooltip[y;Field is only used by absolute and relative coordinates.]"..
448 "field[0.2,0;6,10;z;z;"..s(fields.z).."]"..
449 "tooltip[z;Field is only used by absolute and relative coordinates.]"..
450 "field[0.2,1;6,10;distance;Distance;"..s(fields.distance).."]"..
451 "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.]"..
452 "field[0.2,2;6,10;seed;Randomness seed;"..seed.."]"..
453 "tooltip[seed;A number used for the random number generators. Identical randomness seeds will produce identical trees. This field is optional.]"..
454 "button[3.5,8;3,1;plant_plant;Plant tree]"..
455 "tooltip[plant_plant;Immediately place the tree at the specified position]"..
456 "button[6.5,8;3,1;sapling;Give me a sapling]"..
457 "tooltip[sapling;This gives you an item which you can place manually in the world later]"
458 else
459 local notreestr = "No tree in database selected or database is empty."
460 if has_lplant_priv then
461 return "label[0,0;"..notreestr.."]"
462 else
463 return "label[0,0;"..notreestr.."\nYou are not allowed to plant trees anyway as you don't have the “lplant” privilege.]"
469 --[[ This creates the cheat sheet tab ]]
470 function ltool.tab_cheat_sheet()
471 return ""..
472 "tablecolumns[text;text]"..
473 "tableoptions[background=#000000;highlight=#000000;border=false]"..
474 "table[-0.15,0.75;12,8;cheat_sheet;"..
475 "Symbol,Action,"..
476 "G,Move forward one unit with the pen up,"..
477 "F,Move forward one unit with the pen down drawing trunks and branches,"..
478 "f,Move forward one unit with the pen down drawing leaves,"..
479 "T,Move forward one unit with the pen down drawing trunks,"..
480 "R,Move forward one unit with the pen down placing fruit,"..
481 "A,Replace with rules set A,"..
482 "B,Replace with rules set B,"..
483 "C,Replace with rules set C,"..
484 "D,Replace with rules set D,"..
485 "a,Replace with rules set A\\, chance 90%,"..
486 "b,Replace with rules set B\\, chance 80%,"..
487 "c,Replace with rules set C\\, chance 70%,"..
488 "d,Replace with rules set D\\, chance 60%,"..
489 "+,Yaw the turtle right by angle parameter,"..
490 "-,Yaw the turtle left by angle parameter,"..
491 "&,Pitch the turtle down by angle parameter,"..
492 "^,Pitch the turtle up by angle parameter,"..
493 "/,Roll the turtle to the right by angle parameter,"..
494 "*,Roll the turtle to the left by angle parameter,"..
495 "\\[,Save in stack current state info,"..
496 "\\],Recover from stack state info]"
499 function ltool.tab_help_intro()
500 return ""..
501 "tablecolumns[text]"..
502 "tableoptions[background=#000000;highlight=#000000;border=false]"..
503 "table[-0.15,0.75;12,7;help_intro;"..
504 string.format("You are using the L-System Tree Utility mod version %s.,", ltool.VERSION.STRING)..
505 ","..
506 "The purpose of this mod is to aid with the creation of L-system trees.,"..
507 "With this mod you can create\\, save\\, manage and plant L-system trees.,"..
508 "All trees are saved into <world path>/ltool.mt on server shutdown.,"..
509 "This mod assumes you already understand the concept of L-systems\\;,"..
510 "this mod is mainly aimed towards modders.,"..
511 ","..
512 "The usual workflow in this mod goes like this:,"..
513 ","..
514 "1. Create a new tree in the \"Edit\" tab and save it,"..
515 "2. Select it in the database,"..
516 "3. Plant it,"..
517 ","..
518 "To help you get started\\, you can create an example tree for the \"Edit\" tab,"..
519 "by pressing this button:]"..
520 "button[4,8;4,1;create_template;Create template]"
523 function ltool.tab_help_edit()
524 return ""..
525 "tablecolumns[text]"..
526 "tableoptions[background=#000000;highlight=#000000;border=false]"..
527 "table[-0.15,0.75;12,8;help_edit;"..
528 "To create a L-system tree\\, switch to the \"Edit\" tab.,"..
529 "When you are done\\, hit \"Save tree to database\". The tree will be stored in,"..
530 "the database. The \"Clear fields\" button empties all the input fields.,"..
531 "To understand the meaning of the fields\\, read the introduction to L-systems.,"..
532 "All trees must have an unique name. You are notified in case there is a name,"..
533 "clash. If the name clash is with one of your own trees\\, you can choose to,"..
534 "replace it.]"
537 function ltool.tab_help_database()
538 return ""..
539 "tablecolumns[text]"..
540 "tableoptions[background=#000000;highlight=#000000;border=false]"..
541 "table[-0.15,0.75;12,8;help_database;"..
542 "The database contains a server-wide list of all created trees.,"..
543 "Each tree has an \"owner\". In this mod\\, the concept of ownership is a very,"..
544 "weak one: The owner may rename\\, change and delete his/her own trees\\,,"..
545 "everyone else is prevented from doing that. In contrast\\, all trees can be,"..
546 "copied freely\\;,"..
547 "To do so\\, simply hit \"Copy tree to editor\"\\, change the name and hit,"..
548 "\"Save tree to database\". If you like someone else's tree definition\\,,"..
549 "it is recommended to make a copy for yourself\\, since the original owner,"..
550 "can at any time choose to delete or edit the tree. The trees which you \"own\","..
551 "are written in a yellow font\\, all other trees in a white font.,"..
552 "In order to plant a tree\\, you have to select a tree in the database first.]"
555 function ltool.tab_help_plant()
556 return ""..
557 "tablecolumns[text]"..
558 "tableoptions[background=#000000;highlight=#000000;border=false]"..
559 "table[-0.15,0.75;12,8;help_plant;"..
560 "To plant a tree from a previously created tree definition\\, first select,"..
561 "it in the database\\, then open the \"Plant\" tab.,"..
562 "In this tab\\, you can directly place the tree or request a sapling.,"..
563 "If you choose to directly place the tree\\, you can either specify absolute,"..
564 "or relative coordinates or specify that the tree should be planted in your,"..
565 "viewing direction. Absolute coordinates are the world coordinates as specified,"..
566 "by the \"x\"\\, \"y\"\\, and \"z\" fields. Relative coordinates are relative,"..
567 "to your position and use the same fields. When you choose to plant the tree,"..
568 "based on your viewing direction\\, the tree will be planted at a distance,"..
569 "specified by the field \"distance\" away from you in the direction you look to.,"..
570 "When using coordinates\\, the \"distance\" field is ignored\\, when using,"..
571 "direction\\, the coordinate fields are ignored.,"..
572 ","..
573 "You can also use the “lplant” server command to plant trees.,"..
574 ","..
575 "If you got a sapling\\, you can place it practically anywhere you like to.,"..
576 "After placing it\\, the sapling will be replaced by the L-system tree after,"..
577 "5 seconds\\, unless it was destroyed in the meantime.,"..
578 "All requested saplings are independent from the moment they are created.,"..
579 "The sapling will still work\\, even if the original tree definiton has been,"..
580 "deleted.]"
583 function ltool.tab_help(index)
584 local formspec = "tabheader[0.1,1;ltool_help_tab;Introduction,Creating Trees,Managing Trees,Planting Trees,Cheat Sheet;"..tostring(index)..";true;false]"
585 if(index==1) then
586 formspec = formspec .. ltool.tab_help_intro()
587 elseif(index==2) then
588 formspec = formspec .. ltool.tab_help_edit()
589 elseif(index==3) then
590 formspec = formspec .. ltool.tab_help_database()
591 elseif(index==4) then
592 formspec = formspec .. ltool.tab_help_plant()
593 elseif(index==5) then
594 formspec = formspec .. ltool.tab_cheat_sheet()
597 return formspec
600 function ltool.formspec_editplus(fragment)
601 local formspec = ""..
602 "size[12,8]"..
603 "textarea[0.2,0.5;12,3;"..fragment.."]"..
604 "label[0,3.625;Draw:]"..
605 "button[2,3.5;1,1;editplus_c_G;G]"..
606 "tooltip[editplus_c_G;Move forward one unit with the pen up]"..
607 "button[3,3.5;1,1;editplus_c_F;F]"..
608 "tooltip[editplus_c_F;Move forward one unit with the pen down drawing trunks and branches]"..
609 "button[4,3.5;1,1;editplus_c_f;f]"..
610 "tooltip[editplus_c_f;Move forward one unit with the pen down drawing leaves]"..
611 "button[5,3.5;1,1;editplus_c_T;T]"..
612 "tooltip[editplus_c_T;Move forward one unit with the pen down drawing trunks]"..
613 "button[6,3.5;1,1;editplus_c_R;R]"..
614 "tooltip[editplus_c_R;Move forward one unit with the pen down placing fruit]"..
616 "label[0,4.625;Rules:]"..
617 "button[2,4.5;1,1;editplus_c_A;A]"..
618 "tooltip[editplus_c_A;Replace with rules set A]"..
619 "button[3,4.5;1,1;editplus_c_B;B]"..
620 "tooltip[editplus_c_B;Replace with rules set B]"..
621 "button[4,4.5;1,1;editplus_c_C;C]"..
622 "tooltip[editplus_c_C;Replace with rules set C]"..
623 "button[5,4.5;1,1;editplus_c_D;D]"..
624 "tooltip[editplus_c_D;Replace with rules set D]"..
625 "button[6.5,4.5;1,1;editplus_c_a;a]"..
626 "tooltip[editplus_c_a;Replace with rules set A, chance 90%]"..
627 "button[7.5,4.5;1,1;editplus_c_b;b]"..
628 "tooltip[editplus_c_b;Replace with rules set B, chance 80%]"..
629 "button[8.5,4.5;1,1;editplus_c_c;c]"..
630 "tooltip[editplus_c_c;Replace with rules set C, chance 70%]"..
631 "button[9.5,4.5;1,1;editplus_c_d;d]"..
632 "tooltip[editplus_c_d;Replace with rules set D, chance 60%]"..
634 "label[0,5.625;Rotate:]"..
635 "button[3,5.5;1,1;editplus_c_+;+]"..
636 "tooltip[editplus_c_+;Yaw the turtle right by the value specified in \"Angle\"]"..
637 "button[2,5.5;1,1;editplus_c_-;-]"..
638 "tooltip[editplus_c_-;Yaw the turtle left by the value specified in \"Angle\"]"..
639 "button[4.5,5.5;1,1;editplus_c_&;&]"..
640 "tooltip[editplus_c_&;Pitch the turtle down by the value specified in \"Angle\"]"..
641 "button[5.5,5.5;1,1;editplus_c_^;^]"..
642 "tooltip[editplus_c_^;Pitch the turtle up by the value specified in \"Angle\"]"..
643 "button[8,5.5;1,1;editplus_c_/;/]"..
644 "tooltip[editplus_c_/;Roll the turtle to the right by the value specified in \"Angle\"]"..
645 "button[7,5.5;1,1;editplus_c_*;*]"..
646 "tooltip[editplus_c_*;Roll the turtle to the left by the value specified in \"Angle\"]"..
648 "label[0,6.625;Stack:]"..
649 "button[2,6.5;1,1;editplus_c_P;\\[]"..
650 "tooltip[editplus_c_P;Save current state info into stack]"..
651 "button[3,6.5;1,1;editplus_c_p;\\]]"..
652 "tooltip[editplus_c_p;Recover from current stack state info]"..
654 "button[2.5,7.5;3,1;editplus_save;Save]"..
655 "button[5.5,7.5;3,1;editplus_cancel;Cancel]"
657 return formspec
660 --[[ creates the content of a textlist which contains all trees.
661 index: Selected entry
662 playername: To which the main formspec is shown to. Used for highlighting owned trees
664 returns (string to be used in the text list, table of tree IDs)
666 function ltool.build_tree_textlist(index, playername)
667 local string = ""
668 local colorstring
669 if(ltool.number_of_trees == 0) then
670 return nil
672 local tree_ids = ltool.get_tree_ids()
673 for i=1,#tree_ids do
674 local tree_id = tree_ids[i]
675 local tree = ltool.trees[tree_id]
676 if(tree.author == playername) then
677 colorstring = "#FFFF00"
678 else
679 colorstring = ""
681 string = string .. colorstring .. tostring(tree_id) .. ": " .. minetest.formspec_escape(tree.name)
682 if(i~=#tree_ids) then
683 string = string .. ","
686 return string, tree_ids
689 --[=[ Here come functions which show formspecs to players ]=]
691 --[[ Shows the main tree form to the given player, starting with the "Edit" tab ]]
692 function ltool.show_treeform(playername)
693 local privs = minetest.get_player_privs(playername)
694 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
695 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
698 --[[ spawns a simple dialog formspec to a player ]]
699 function ltool.show_dialog(playername, formname, message)
700 local formspec = "size[12,2;]label[0,0.2;"..message.."]"..
701 "button[4.5,1.5;3,1;okay;OK]"
702 minetest.show_formspec(playername, formname, formspec)
707 --[=[ End of formspec-relatec functions ]=]
709 --[[ This function does a lot of parameter checks and returns (tree, tree_name) on success.
710 If ANY parameter check fails, the whole function fails.
711 On failure, it returns (nil, <error message string>).]]
712 function ltool.evaluate_edit_fields(fields, ignore_name)
713 local treedef = {}
714 -- Validation helper: Checks for invalid characters for the fields “axiom” and the 4 rule sets
715 local v = function(str)
716 local match = string.match(str, "[^][abcdfABCDFGTR+-/*&^]")
717 if(match==nil) then
718 return true
719 else
720 return false
723 -- Validation helper: Checks for balanced brackets
724 local b = function(str)
725 local brackets = 0
726 for c=1, string.len(str) do
727 local char = string.sub(str, c, c)
728 if char == "[" then
729 brackets = brackets + 1
730 elseif char == "]" then
731 brackets = brackets - 1
732 if brackets < 0 then
733 return false
737 return brackets == 0
740 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
741 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
742 treedef.rules_a = fields.rules_a
743 treedef.rules_b = fields.rules_b
744 treedef.rules_c = fields.rules_c
745 treedef.rules_d = fields.rules_d
746 treedef.axiom = fields.axiom
747 else
748 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."
750 else
751 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."
753 treedef.trunk = fields.trunk
754 treedef.leaves = fields.leaves
755 treedef.leaves2 = fields.leaves2
756 treedef.leaves2_chance = fields.leaves2_chance
757 treedef.angle = tonumber(fields.angle)
758 if(treedef.angle == nil) then
759 return nil, "The field \"Angle\" must contain a number."
761 treedef.iterations = tonumber(fields.iterations)
762 if(treedef.iterations == nil) then
763 return nil, "The field \"Iterations\" must contain a natural number greater or equal to 0."
764 elseif(treedef.iterations < 0) then
765 return nil, "The field \"Iterations\" must contain a natural number greater or equal to 0."
767 treedef.random_level = tonumber(fields.random_level)
768 if(treedef.random_level == nil) then
769 return nil, "The field \"Randomness level\" must contain a number."
771 treedef.fruit = fields.fruit
772 treedef.fruit_chance = tonumber(fields.fruit_chance)
773 if(treedef.fruit_chance == nil) then
774 return nil, "The field \"Fruit chance\" must contain a number."
775 elseif(treedef.fruit_chance > 100 or treedef.fruit_chance < 0) then
776 return nil, "Fruit chance must be between 0% and 100%."
778 if(fields.trunk_type == "single" or fields.trunk_type == "double" or fields.trunk_type == "crossed") then
779 treedef.trunk_type = fields.trunk_type
780 else
781 return nil, "Trunk type must be \"single\", \"double\" or \"crossed\"."
783 treedef.thin_branches = fields.thin_branches
784 if(fields.thin_branches == "true") then
785 treedef.thin_branches = true
786 elseif(fields.thin_branches == "false") then
787 treedef.thin_branches = false
788 else
789 return nil, "Field \"Thin branches?\" must be \"true\" or \"false\"."
791 local name = fields.name
792 if(ignore_name ~= true and name == "") then
793 return nil, "Name is empty."
795 return treedef, name
799 --[=[ Here come several utility functions ]=]
801 --[[ converts a given tree to field names, as if they were given to a
802 minetest.register_on_plyer_receive_fields callback function ]]
803 function ltool.tree_to_fields(tree)
804 local s = function(i)
805 if(i==nil) then
806 return ""
807 else
808 return tostring(i)
811 local fields = {}
812 fields.axiom = s(tree.treedef.axiom)
813 fields.rules_a = s(tree.treedef.rules_a)
814 fields.rules_b = s(tree.treedef.rules_b)
815 fields.rules_c = s(tree.treedef.rules_c)
816 fields.rules_d = s(tree.treedef.rules_d)
817 fields.trunk = s(tree.treedef.trunk)
818 fields.leaves = s(tree.treedef.leaves)
819 fields.leaves2 = s(tree.treedef.leaves2)
820 fields.leaves2_chance = s(tree.treedef.leaves2)
821 fields.fruit = s(tree.treedef.fruit)
822 fields.fruit_chance = s(tree.treedef.fruit_chance)
823 fields.angle = s(tree.treedef.angle)
824 fields.iterations = s(tree.treedef.iterations)
825 fields.random_level = s(tree.treedef.random_level)
826 fields.trunk_type = s(tree.treedef.trunk_type)
827 fields.thin_branches = s(tree.treedef.thin_branches)
828 fields.name = s(tree.name)
829 return fields
834 -- returns a simple table of all the tree IDs
835 function ltool.get_tree_ids()
836 local ids = {}
837 for tree_id, _ in pairs(ltool.trees) do
838 table.insert(ids, tree_id)
840 table.sort(ids)
841 return ids
844 --[[ In a table of tree IDs (returned by ltool.get_tree_ids, parameter tree_ids), this function
845 searches for the first occourance of the value searched_tree_id and returns its index.
846 This is basically a reverse lookup utility. ]]
847 function ltool.get_tree_id_index(searched_tree_id, tree_ids)
848 for i=1, #tree_ids do
849 local table_tree_id = tree_ids[i]
850 if(searched_tree_id == table_tree_id) then
851 return i
856 -- Returns the selected tree of the given player
857 function ltool.get_selected_tree(playername)
858 local sel = ltool.playerinfos[playername].dbsel
859 if(sel ~= nil) then
860 local tree_id = ltool.playerinfos[playername].treeform.database.textlist[sel]
861 if(tree_id ~= nil) then
862 return ltool.trees[tree_id]
865 return nil
868 -- Returns the ID of the selected tree of the given player
869 function ltool.get_selected_tree_id(playername)
870 local sel = ltool.playerinfos[playername].dbsel
871 if(sel ~= nil) then
872 return ltool.playerinfos[playername].treeform.database.textlist[sel]
874 return nil
878 ltool.treeform = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit()
880 minetest.register_chatcommand("treeform",
882 params = "",
883 description = "Open L-System Tree Utility.",
884 privs = {},
885 func = function(playername, param)
886 ltool.show_treeform(playername)
890 minetest.register_chatcommand("lplant",
892 description = "Plant a L-system tree at the specified position",
893 privs = { lplant = true },
894 params = "<tree ID> <x> <y> <z> [<seed>]",
895 func = function(playername, param)
896 local p = {}
897 local tree_id, x, y, z, seed = string.match(param, "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *([%d.-]*)")
898 tree_id, p.x, p.y, p.z, seed = tonumber(tree_id), tonumber(x), tonumber(y), tonumber(z), tonumber(seed)
899 if not tree_id or not p.x or not p.y or not p.z then
900 return false, "Invalid usage, see /help lplant."
902 local lm = tonumber(minetest.settings:get("map_generation_limit") or 31000)
903 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
904 return false, "Cannot plant tree out of map bounds!"
907 local success = ltool.plant_tree(tree_id, p, seed)
908 if success == false then
909 return false, "Unknown tree ID!"
910 else
911 return true
916 function ltool.dbsel_to_tree(dbsel, playername)
917 return ltool.trees[ltool.playerinfos[playername].treeform.database.textlist[dbsel]]
920 function ltool.save_fields(playername,formname,fields)
921 if(formname=="ltool:treeform_edit") then
922 ltool.playerinfos[playername].treeform.edit.fields = fields
923 elseif(formname=="ltool:treeform_database") then
924 ltool.playerinfos[playername].treeform.database.fields = fields
925 elseif(formname=="ltool:treeform_plant") then
926 ltool.playerinfos[playername].treeform.plant.fields = fields
930 --[=[ Callback functions start here ]=]
932 function ltool.process_form(player,formname,fields)
933 local playername = player:get_player_name()
935 local seltree = ltool.get_selected_tree(playername)
936 local seltree_id = ltool.get_selected_tree_id(playername)
937 local privs = minetest.get_player_privs(playername)
938 local s = function(input)
939 local ret
940 if(input==nil) then
941 ret = ""
942 else
943 ret = minetest.formspec_escape(tostring(input))
945 return ret
947 --[[ process clicks on the tab header ]]
948 if(formname == "ltool:treeform_edit" or formname == "ltool:treeform_database" or formname == "ltool:treeform_plant" or formname == "ltool:treeform_help") then
949 if fields.ltool_tab ~= nil then
950 ltool.save_fields(playername, formname, fields)
951 local tab = tonumber(fields.ltool_tab)
952 local formspec, subformname, contents
953 if(tab==1) then
954 contents = ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
955 subformname = "edit"
956 elseif(tab==2) then
957 contents = ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
958 subformname = "database"
959 elseif(tab==3) then
960 if(ltool.number_of_trees > 0) then
961 contents = ltool.tab_plant(seltree, ltool.playerinfos[playername].treeform.plant.fields, privs.lplant)
962 else
963 contents = ltool.tab_plant(nil, nil, privs.lplant)
965 subformname = "plant"
966 elseif(tab==4) then
967 contents = ltool.tab_help(ltool.playerinfos[playername].treeform.help.tab)
968 subformname = "help"
970 formspec = ltool.formspec_size..ltool.formspec_header(tab)..contents
971 minetest.show_formspec(playername, "ltool:treeform_" .. subformname, formspec)
972 return
975 --[[ "Plant" tab ]]
976 if(formname == "ltool:treeform_plant") then
977 if(fields.plant_plant) then
978 if(seltree ~= nil) then
979 if(privs.lplant ~= true) then
980 ltool.save_fields(playername, formname, fields)
981 local message = "You can't plant trees, you need to have the \"lplant\" privilege."
982 ltool.show_dialog(playername, "ltool:treeform_error_lplant", message)
983 return
985 minetest.log("action","[ltool] Planting tree")
986 local treedef = seltree.treedef
988 local x,y,z = tonumber(fields.x), tonumber(fields.y), tonumber(fields.z)
989 local distance = tonumber(fields.distance)
990 local tree_pos
991 local fail_coordinates = function()
992 ltool.save_fields(playername, formname, fields)
993 ltool.show_dialog(playername, "ltool:treeform_error_badplantfields", "Error: When using coordinates, you have to specifiy numbers in the fields \"x\", \"y\", \"z\".")
995 local fail_distance = function()
996 ltool.save_fields(playername, formname, fields)
997 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\".")
999 if(fields.plantmode == "Absolute coordinates") then
1000 if(type(x)~="number" or type(y) ~= "number" or type(z) ~= "number") then
1001 fail_coordinates()
1002 return
1004 tree_pos = {x=x, y=y, z=z}
1005 elseif(fields.plantmode == "Relative coordinates") then
1006 if(type(x)~="number" or type(y) ~= "number" or type(z) ~= "number") then
1007 fail_coordinates()
1008 return
1010 tree_pos = player:getpos()
1011 tree_pos.x = tree_pos.x + x
1012 tree_pos.y = tree_pos.y + y
1013 tree_pos.z = tree_pos.z + z
1014 elseif(fields.plantmode == "Distance in viewing direction") then
1015 if(type(distance)~="number") then
1016 fail_distance()
1017 return
1019 tree_pos = vector.round(vector.add(player:getpos(), vector.multiply(player:get_look_dir(), distance)))
1020 else
1021 minetest.log("error", "[ltool] fields.plantmode = "..tostring(fields.plantmode))
1024 if(tonumber(fields.seed)~=nil) then
1025 treedef.seed = tonumber(fields.seed)
1028 ltool.plant_tree(seltree_id, tree_pos)
1030 treedef.seed = nil
1032 elseif(fields.sapling) then
1033 if(seltree ~= nil) then
1034 if(privs.lplant ~= true) then
1035 ltool.save_fields(playername, formname, fields)
1036 local message = "You can't request saplings, you need to have the \"lplant\" privilege."
1037 ltool.show_dialog(playername, "ltool:treeform_error_sapling", message)
1038 return
1040 local seed = nil
1041 if(tonumber(fields.seed)~=nil) then
1042 seed = tonumber(fields.seed)
1044 local ret, ret2 = ltool.give_sapling(ltool.trees[seltree_id].treedef, seed, playername, true, ltool.trees[seltree_id].name)
1045 if(ret==false and ret2==2) then
1046 ltool.save_fields(playername, formname, fields)
1047 ltool.show_dialog(playername, "ltool:treeform_error_sapling", "Error: The sapling could not be given to you. Probably your inventory is full.")
1051 --[[ "Edit" tab ]]
1052 elseif(formname == "ltool:treeform_edit") then
1053 if(fields.edit_save or fields.edit_sapling) then
1054 local param1, param2
1055 param1, param2 = ltool.evaluate_edit_fields(fields, fields.edit_sapling ~= nil)
1056 if(fields.edit_save and privs.ledit ~= true) then
1057 ltool.save_fields(playername, formname, fields)
1058 local message = "You can't save trees, you need to have the \"ledit\" privilege."
1059 ltool.show_dialog(playername, "ltool:treeform_error_ledit", message)
1060 return
1062 if(fields.edit_sapling and privs.lplant ~= true) then
1063 ltool.save_fields(playername, formname, fields)
1064 local message = "You can't request saplings, you need to have the \"lplant\" privilege."
1065 ltool.show_dialog(playername, "ltool:treeform_error_ledit", message)
1066 return
1068 local tree_ok = true
1069 local treedef, name
1070 if(param1 ~= nil) then
1071 treedef = param1
1072 name = param2
1073 for k,v in pairs(ltool.trees) do
1074 if(fields.edit_save and v.name == name) then
1075 ltool.save_fields(playername, formname, fields)
1076 if(v.author == playername) then
1077 local formspec = "size[6,2;]label[0,0.2;You already have a tree with this name.\nDo you want to replace it?]"..
1078 "button[0,1.5;3,1;replace_yes;Yes]"..
1079 "button[3,1.5;3,1;replace_no;No]"
1080 minetest.show_formspec(playername, "ltool:treeform_replace", formspec)
1081 else
1082 ltool.show_dialog(playername, "ltool:treeform_error_nameclash", "Error: This name is already taken by someone else.\nPlease choose a different name.")
1084 return
1087 else
1088 tree_ok = false
1090 ltool.save_fields(playername, formname, fields)
1091 if(tree_ok == true) then
1092 if fields.edit_save then
1093 ltool.add_tree(name, playername, treedef)
1094 elseif fields.edit_sapling then
1095 local ret, ret2 = ltool.give_sapling(treedef, tostring(ltool.seed), playername, true, fields.name)
1096 if(ret==false and ret2==2) then
1097 ltool.save_fields(playername, formname, fields)
1098 ltool.show_dialog(playername, "ltool:treeform_error_sapling", "Error: The sapling could not be given to you. Probably your inventory is full.")
1101 else
1102 local message = "Error: The tree definition is invalid.\n"..
1103 minetest.formspec_escape(param2)
1104 ltool.show_dialog(playername, "ltool:treeform_error_badtreedef", message)
1107 if(fields.edit_clear) then
1108 local privs = minetest.get_player_privs(playername)
1109 ltool.save_fields(playername, formname, {})
1110 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(nil, privs.ledit, privs.lplant)
1111 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1113 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
1114 local fragment
1115 if(fields.edit_axiom) then
1116 fragment = "axiom;Axiom;"..s(fields.axiom)
1117 elseif(fields.edit_rules_a) then
1118 fragment = "rules_a;Rules set A;"..s(fields.rules_a)
1119 elseif(fields.edit_rules_b) then
1120 fragment = "rules_b;Rules set B;"..s(fields.rules_b)
1121 elseif(fields.edit_rules_c) then
1122 fragment = "rules_c;Rules set C;"..s(fields.rules_c)
1123 elseif(fields.edit_rules_d) then
1124 fragment = "rules_d;Rules set D;"..s(fields.rules_d)
1127 ltool.save_fields(playername, formname, fields)
1128 local formspec = ltool.formspec_editplus(fragment)
1129 minetest.show_formspec(playername, "ltool:treeform_editplus", formspec)
1131 if(mod_select_item and (fields.edit_trunk or fields.edit_leaves or fields.edit_leaves2 or fields.edit_fruit)) then
1132 ltool.save_fields(playername, formname, fields)
1133 select_item.show_dialog(playername, "ltool:node", function(itemstring)
1134 if itemstring ~= "air" and minetest.registered_nodes[itemstring] ~= nil then
1135 return true
1137 end)
1139 --[[ Larger edit fields for axiom and rules fields ]]
1140 elseif(formname == "ltool:treeform_editplus") then
1141 local editfields = ltool.playerinfos[playername].treeform.edit.fields
1142 local function addchar(c)
1143 local fragment
1144 if(c=="P") then c = "[" end
1145 if(c=="p") then c = "]" end
1146 if(fields.axiom) then
1147 fragment = "axiom;Axiom;"..s(fields.axiom..c)
1148 elseif(fields.rules_a) then
1149 fragment = "rules_a;Rules set A;"..s(fields.rules_a..c)
1150 elseif(fields.rules_b) then
1151 fragment = "rules_b;Rules set B;"..s(fields.rules_b..c)
1152 elseif(fields.rules_c) then
1153 fragment = "rules_c;Rules set C;"..s(fields.rules_c..c)
1154 elseif(fields.rules_d) then
1155 fragment = "rules_d;Rules set D;"..s(fields.rules_d..c)
1157 local formspec = ltool.formspec_editplus(fragment)
1158 minetest.show_formspec(playername, "ltool:treeform_editplus", formspec)
1160 if(fields.editplus_save) then
1161 local function o(writed, writer)
1162 if(writer~=nil) then
1163 return writer
1164 else
1165 return writed
1168 editfields.axiom = o(editfields.axiom, fields.axiom)
1169 editfields.rules_a = o(editfields.rules_a, fields.rules_a)
1170 editfields.rules_b = o(editfields.rules_b, fields.rules_b)
1171 editfields.rules_c = o(editfields.rules_c, fields.rules_c)
1172 editfields.rules_d = o(editfields.rules_d, fields.rules_d)
1173 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1174 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1175 elseif(fields.editplus_cancel or fields.quit) then
1176 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1177 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1178 else
1179 for id, field in pairs(fields) do
1180 if(string.sub(id,1,11) == "editplus_c_") then
1181 local char = string.sub(id,12,12)
1182 addchar(char)
1186 --[[ "Database" tab ]]
1187 elseif(formname == "ltool:treeform_database") then
1188 if(fields.treelist) then
1189 local event = minetest.explode_textlist_event(fields.treelist)
1190 if(event.type == "CHG") then
1191 ltool.playerinfos[playername].dbsel = event.index
1192 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(event.index, playername)
1193 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1195 elseif(fields.database_copy) then
1196 if(seltree ~= nil) then
1197 if(ltool.playerinfos[playername] ~= nil) then
1198 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.tree_to_fields(seltree), privs.ledit, privs.lplant)
1199 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1201 else
1202 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", "Error: No tree is selected.")
1204 elseif(fields.database_update) then
1205 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1206 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1208 elseif(fields.database_delete) then
1209 if(privs.ledit ~= true) then
1210 ltool.save_fields(playername, formname, fields)
1211 local message = "You can't delete trees, you need to have the \"ledit\" privilege."
1212 ltool.show_dialog(playername, "ltool:treeform_error_ledit_db", message)
1213 return
1215 if(seltree ~= nil) then
1216 if(playername == seltree.author) then
1217 local remove_id = ltool.get_selected_tree_id(playername)
1218 if(remove_id ~= nil) then
1219 ltool.remove_tree(remove_id)
1220 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1221 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1223 else
1224 ltool.show_dialog(playername, "ltool:treeform_error_delete", "Error: This tree is not your own. You may only delete your own trees.")
1226 else
1227 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", "Error: No tree is selected.")
1229 elseif(fields.database_rename) then
1230 if(seltree ~= nil) then
1231 if(privs.ledit ~= true) then
1232 ltool.save_fields(playername, formname, fields)
1233 local message = "You can't rename trees, you need to have the \"ledit\" privilege."
1234 ltool.show_dialog(playername, "ltool:treeform_error_ledit_db", message)
1235 return
1237 if(playername == seltree.author) then
1238 local formspec = "field[newname;New name:;"..minetest.formspec_escape(seltree.name).."]"
1239 minetest.show_formspec(playername, "ltool:treeform_rename", formspec)
1240 else
1241 ltool.show_dialog(playername, "ltool:treeform_error_rename_forbidden", "Error: This tree is not your own. You may only rename your own trees.")
1243 else
1244 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", "Error: No tree is selected.")
1247 --[[ Process "Do you want to replace this tree?" dialog ]]
1248 elseif(formname == "ltool:treeform_replace") then
1249 local editfields = ltool.playerinfos[playername].treeform.edit.fields
1250 local newtreedef, newname = ltool.evaluate_edit_fields(editfields)
1251 if(privs.ledit ~= true) then
1252 local message = "You can't overwrite trees, you need to have the \"ledit\" privilege."
1253 minetest.show_dialog(playername, "ltool:treeform_error_ledit", message)
1254 return
1256 if(fields.replace_yes) then
1257 for tree_id,tree in pairs(ltool.trees) do
1258 if(tree.name == newname) then
1259 --[[ The old tree is deleted and a
1260 new one with a new ID is created ]]
1261 local new_tree_id = ltool.next_tree_id
1262 ltool.trees[new_tree_id] = {}
1263 ltool.trees[new_tree_id].treedef = newtreedef
1264 ltool.trees[new_tree_id].name = newname
1265 ltool.trees[new_tree_id].author = tree.author
1266 ltool.next_tree_id = ltool.next_tree_id + 1
1267 ltool.trees[tree_id] = nil
1268 ltool.playerinfos[playername].dbsel = ltool.number_of_trees
1272 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1273 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1274 elseif(formname == "ltool:treeform_help") then
1275 local tab = tonumber(fields.ltool_help_tab)
1276 if(tab ~= nil) then
1277 ltool.playerinfos[playername].treeform.help.tab = tab
1278 local formspec = ltool.formspec_size..ltool.formspec_header(4)..ltool.tab_help(tab)
1279 minetest.show_formspec(playername, "ltool:treeform_help", formspec)
1281 if(fields.create_template) then
1282 local newfields = {
1283 axiom="FFFFFAFFBF",
1284 rules_a="[&&&FFFFF&&FFFF][&&&++++FFFFF&&FFFF][&&&----FFFFF&&FFFF]",
1285 rules_b="[&&&++FFFFF&&FFFF][&&&--FFFFF&&FFFF][&&&------FFFFF&&FFFF]",
1286 trunk="mapgen_tree",
1287 leaves="mapgen_leaves",
1288 angle="30",
1289 iterations="2",
1290 random_level="0",
1291 trunk_type="single",
1292 thin_branches="true",
1293 fruit_chance="10",
1294 fruit="mapgen_apple",
1295 name = "Example Tree "..ltool.next_tree_id
1297 ltool.save_fields(playername, formname, newfields)
1298 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(newfields, privs.ledit, privs.lplant)
1299 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1301 --[[ Tree renaming dialog ]]
1302 elseif(formname == "ltool:treeform_rename") then
1303 if(privs.ledit ~= true) then
1304 ltool.save_fields(playername, formname, fields)
1305 local message = "You can't delete trees, you need to have the \"ledit\" privilege."
1306 ltool.show_dialog(playername, "ltool:treeform_error_ledit_delete", message)
1307 return
1309 if(fields.newname ~= "" and fields.newname ~= nil) then
1310 ltool.rename_tree(ltool.get_selected_tree_id(playername), fields.newname)
1311 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1312 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1313 else
1314 ltool.show_dialog(playername, "ltool:treeform_error_bad_rename", "Error: This name is empty. The tree name must be non-empty.")
1316 --[[ Here come various error messages to handle ]]
1317 elseif(formname == "ltool:treeform_error_badtreedef" or formname == "ltool:treeform_error_nameclash" or formname == "ltool:treeform_error_ledit") then
1318 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
1319 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1320 elseif(formname == "ltool:treeform_error_badplantfields" or formname == "ltool:treeform_error_sapling" or formname == "ltool:treeform_error_lplant") then
1321 local formspec = ltool.formspec_size..ltool.formspec_header(3)..ltool.tab_plant(seltree, ltool.playerinfos[playername].treeform.plant.fields, privs.lplant)
1322 minetest.show_formspec(playername, "ltool:treeform_plant", formspec)
1323 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
1324 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1325 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1326 elseif(formname == "ltool:treeform_error_bad_rename") then
1327 local formspec = "field[newname;New name:;"..minetest.formspec_escape(seltree.name).."]"
1328 minetest.show_formspec(playername, "ltool:treeform_rename", formspec)
1329 else
1330 -- Action for Inventory++ button
1331 if fields.ltool and minetest.get_modpath("inventory_plus") then
1332 ltool.show_treeform(playername)
1333 return
1338 if mod_select_item then
1339 select_item.register_on_select_item(function(playername, dialogname, itemstring)
1340 if dialogname == "ltool:node" then
1341 if itemstring then
1342 local f = ltool.playerinfos[playername].treeform.edit.fields
1343 if f.edit_trunk then
1344 f.trunk = itemstring
1345 elseif f.edit_leaves then
1346 f.leaves = itemstring
1347 elseif f.edit_leaves2 then
1348 f.leaves2 = itemstring
1349 elseif f.edit_fruit then
1350 f.fruit = itemstring
1353 ltool.show_treeform(playername)
1354 return false
1356 end)
1359 --[[ These 2 functions are basically just table initializions and cleanups ]]
1360 function ltool.leave(player)
1361 ltool.playerinfos[player:get_player_name()] = nil
1364 function ltool.join(player)
1365 local infotable = {}
1366 infotable.dbsel = nil
1367 infotable.treeform = {}
1368 infotable.treeform.database = {}
1369 --[[ This table stores a mapping of the textlist IDs in the database formspec and the tree IDs.
1370 It is updated each time ltool.tab_database is called. ]]
1371 infotable.treeform.database.textlist = {}
1372 --[[ the “fields” tables store the values of the input fields of a formspec. It is updated
1373 whenever the formspec is changed, i.e. on tab change ]]
1374 infotable.treeform.database.fields = {}
1375 infotable.treeform.plant = {}
1376 infotable.treeform.plant.fields = {}
1377 infotable.treeform.edit = {}
1378 infotable.treeform.edit.fields = {}
1379 infotable.treeform.help = {}
1380 infotable.treeform.help.tab = 1
1381 ltool.playerinfos[player:get_player_name()] = infotable
1383 -- Add Inventory++ support
1384 if minetest.get_modpath("inventory_plus") then
1385 inventory_plus.register_button(player, "ltool", "L-System Tree Utility")
1389 function ltool.save_to_file()
1390 local savetable = {}
1391 savetable.trees = ltool.trees
1392 savetable.number_of_trees = ltool.number_of_trees
1393 savetable.next_tree_id = ltool.next_tree_id
1394 local savestring = minetest.serialize(savetable)
1395 local filepath = minetest.get_worldpath().."/ltool.mt"
1396 local file = io.open(filepath, "w")
1397 if(file) then
1398 file:write(savestring)
1399 io.close(file)
1400 minetest.log("action", "[ltool] Tree data saved to "..filepath..".")
1401 else
1402 minetest.log("error", "[ltool] Failed to write ltool data to "..filepath".")
1407 minetest.register_on_player_receive_fields(ltool.process_form)
1409 minetest.register_on_leaveplayer(ltool.leave)
1411 minetest.register_on_joinplayer(ltool.join)
1413 minetest.register_on_shutdown(ltool.save_to_file)
1415 local button_action = function(player)
1416 ltool.show_treeform(player:get_player_name())
1419 if minetest.get_modpath("unified_inventory") ~= nil then
1420 unified_inventory.register_button("ltool", {
1421 type = "image",
1422 image = "ltool_sapling.png",
1423 tooltip = "L-System Tree Utility",
1424 action = button_action,
1428 if minetest.get_modpath("sfinv_buttons") ~= nil then
1429 sfinv_buttons.register_button("ltool", {
1430 title = "L-System Tree Utility",
1431 tooltip = "Invent your own trees and plant them",
1432 image = "ltool_sapling.png",
1433 action = button_action,