Version 1.4.1
[minetest_ltool.git] / init.lua
blobe838875470b50ce8922d500e8f830f341afeed52
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 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
724 treedef.rules_a = fields.rules_a
725 treedef.rules_b = fields.rules_b
726 treedef.rules_c = fields.rules_c
727 treedef.rules_d = fields.rules_d
728 treedef.axiom = fields.axiom
729 else
730 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."
732 treedef.trunk = fields.trunk
733 treedef.leaves = fields.leaves
734 treedef.leaves2 = fields.leaves2
735 treedef.leaves2_chance = fields.leaves2_chance
736 treedef.angle = tonumber(fields.angle)
737 if(treedef.angle == nil) then
738 return nil, "The field \"Angle\" must contain a number."
740 treedef.iterations = tonumber(fields.iterations)
741 if(treedef.iterations == nil) then
742 return nil, "The field \"Iterations\" must contain a natural number greater or equal to 0."
743 elseif(treedef.iterations < 0) then
744 return nil, "The field \"Iterations\" must contain a natural number greater or equal to 0."
746 treedef.random_level = tonumber(fields.random_level)
747 if(treedef.random_level == nil) then
748 return nil, "The field \"Randomness level\" must contain a number."
750 treedef.fruit = fields.fruit
751 treedef.fruit_chance = tonumber(fields.fruit_chance)
752 if(treedef.fruit_chance == nil) then
753 return nil, "The field \"Fruit chance\" must contain a number."
754 elseif(treedef.fruit_chance > 100 or treedef.fruit_chance < 0) then
755 return nil, "Fruit chance must be between 0% and 100%."
757 if(fields.trunk_type == "single" or fields.trunk_type == "double" or fields.trunk_type == "crossed") then
758 treedef.trunk_type = fields.trunk_type
759 else
760 return nil, "Trunk type must be \"single\", \"double\" or \"crossed\"."
762 treedef.thin_branches = fields.thin_branches
763 if(fields.thin_branches == "true") then
764 treedef.thin_branches = true
765 elseif(fields.thin_branches == "false") then
766 treedef.thin_branches = false
767 else
768 return nil, "Field \"Thin branches?\" must be \"true\" or \"false\"."
770 local name = fields.name
771 if(ignore_name ~= true and name == "") then
772 return nil, "Name is empty."
774 return treedef, name
778 --[=[ Here come several utility functions ]=]
780 --[[ converts a given tree to field names, as if they were given to a
781 minetest.register_on_plyer_receive_fields callback function ]]
782 function ltool.tree_to_fields(tree)
783 local s = function(i)
784 if(i==nil) then
785 return ""
786 else
787 return tostring(i)
790 local fields = {}
791 fields.axiom = s(tree.treedef.axiom)
792 fields.rules_a = s(tree.treedef.rules_a)
793 fields.rules_b = s(tree.treedef.rules_b)
794 fields.rules_c = s(tree.treedef.rules_c)
795 fields.rules_d = s(tree.treedef.rules_d)
796 fields.trunk = s(tree.treedef.trunk)
797 fields.leaves = s(tree.treedef.leaves)
798 fields.leaves2 = s(tree.treedef.leaves2)
799 fields.leaves2_chance = s(tree.treedef.leaves2)
800 fields.fruit = s(tree.treedef.fruit)
801 fields.fruit_chance = s(tree.treedef.fruit_chance)
802 fields.angle = s(tree.treedef.angle)
803 fields.iterations = s(tree.treedef.iterations)
804 fields.random_level = s(tree.treedef.random_level)
805 fields.trunk_type = s(tree.treedef.trunk_type)
806 fields.thin_branches = s(tree.treedef.thin_branches)
807 fields.name = s(tree.name)
808 return fields
813 -- returns a simple table of all the tree IDs
814 function ltool.get_tree_ids()
815 local ids = {}
816 for tree_id, _ in pairs(ltool.trees) do
817 table.insert(ids, tree_id)
819 table.sort(ids)
820 return ids
823 --[[ In a table of tree IDs (returned by ltool.get_tree_ids, parameter tree_ids), this function
824 searches for the first occourance of the value searched_tree_id and returns its index.
825 This is basically a reverse lookup utility. ]]
826 function ltool.get_tree_id_index(searched_tree_id, tree_ids)
827 for i=1, #tree_ids do
828 local table_tree_id = tree_ids[i]
829 if(searched_tree_id == table_tree_id) then
830 return i
835 -- Returns the selected tree of the given player
836 function ltool.get_selected_tree(playername)
837 local sel = ltool.playerinfos[playername].dbsel
838 if(sel ~= nil) then
839 local tree_id = ltool.playerinfos[playername].treeform.database.textlist[sel]
840 if(tree_id ~= nil) then
841 return ltool.trees[tree_id]
844 return nil
847 -- Returns the ID of the selected tree of the given player
848 function ltool.get_selected_tree_id(playername)
849 local sel = ltool.playerinfos[playername].dbsel
850 if(sel ~= nil) then
851 return ltool.playerinfos[playername].treeform.database.textlist[sel]
853 return nil
857 ltool.treeform = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit()
859 minetest.register_chatcommand("treeform",
861 params = "",
862 description = "Open L-System Tree Utility.",
863 privs = {},
864 func = function(playername, param)
865 ltool.show_treeform(playername)
869 minetest.register_chatcommand("lplant",
871 description = "Plant a L-system tree at the specified position",
872 privs = { lplant = true },
873 params = "<tree ID> <x> <y> <z> [<seed>]",
874 func = function(playername, param)
875 local p = {}
876 local tree_id, x, y, z, seed = string.match(param, "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *([%d.-]*)")
877 tree_id, p.x, p.y, p.z, seed = tonumber(tree_id), tonumber(x), tonumber(y), tonumber(z), tonumber(seed)
878 if not tree_id or not p.x or not p.y or not p.z then
879 return false, "Invalid usage, see /help lplant."
881 local lm = tonumber(minetest.settings:get("map_generation_limit") or 31000)
882 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
883 return false, "Cannot plant tree out of map bounds!"
886 local success = ltool.plant_tree(tree_id, p, seed)
887 if success == false then
888 return false, "Unknown tree ID!"
889 else
890 return true
895 function ltool.dbsel_to_tree(dbsel, playername)
896 return ltool.trees[ltool.playerinfos[playername].treeform.database.textlist[dbsel]]
899 function ltool.save_fields(playername,formname,fields)
900 if(formname=="ltool:treeform_edit") then
901 ltool.playerinfos[playername].treeform.edit.fields = fields
902 elseif(formname=="ltool:treeform_database") then
903 ltool.playerinfos[playername].treeform.database.fields = fields
904 elseif(formname=="ltool:treeform_plant") then
905 ltool.playerinfos[playername].treeform.plant.fields = fields
909 --[=[ Callback functions start here ]=]
911 function ltool.process_form(player,formname,fields)
912 local playername = player:get_player_name()
914 local seltree = ltool.get_selected_tree(playername)
915 local seltree_id = ltool.get_selected_tree_id(playername)
916 local privs = minetest.get_player_privs(playername)
917 local s = function(input)
918 local ret
919 if(input==nil) then
920 ret = ""
921 else
922 ret = minetest.formspec_escape(tostring(input))
924 return ret
926 --[[ process clicks on the tab header ]]
927 if(formname == "ltool:treeform_edit" or formname == "ltool:treeform_database" or formname == "ltool:treeform_plant" or formname == "ltool:treeform_help") then
928 if fields.ltool_tab ~= nil then
929 ltool.save_fields(playername, formname, fields)
930 local tab = tonumber(fields.ltool_tab)
931 local formspec, subformname, contents
932 if(tab==1) then
933 contents = ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
934 subformname = "edit"
935 elseif(tab==2) then
936 contents = ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
937 subformname = "database"
938 elseif(tab==3) then
939 if(ltool.number_of_trees > 0) then
940 contents = ltool.tab_plant(seltree, ltool.playerinfos[playername].treeform.plant.fields, privs.lplant)
941 else
942 contents = ltool.tab_plant(nil, nil, privs.lplant)
944 subformname = "plant"
945 elseif(tab==4) then
946 contents = ltool.tab_help(ltool.playerinfos[playername].treeform.help.tab)
947 subformname = "help"
949 formspec = ltool.formspec_size..ltool.formspec_header(tab)..contents
950 minetest.show_formspec(playername, "ltool:treeform_" .. subformname, formspec)
951 return
954 --[[ "Plant" tab ]]
955 if(formname == "ltool:treeform_plant") then
956 if(fields.plant_plant) then
957 if(seltree ~= nil) then
958 if(privs.lplant ~= true) then
959 ltool.save_fields(playername, formname, fields)
960 local message = "You can't plant trees, you need to have the \"lplant\" privilege."
961 ltool.show_dialog(playername, "ltool:treeform_error_lplant", message)
962 return
964 minetest.log("action","[ltool] Planting tree")
965 local treedef = seltree.treedef
967 local x,y,z = tonumber(fields.x), tonumber(fields.y), tonumber(fields.z)
968 local distance = tonumber(fields.distance)
969 local tree_pos
970 local fail_coordinates = function()
971 ltool.save_fields(playername, formname, fields)
972 ltool.show_dialog(playername, "ltool:treeform_error_badplantfields", "Error: When using coordinates, you have to specifiy numbers in the fields \"x\", \"y\", \"z\".")
974 local fail_distance = function()
975 ltool.save_fields(playername, formname, fields)
976 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\".")
978 if(fields.plantmode == "Absolute coordinates") then
979 if(type(x)~="number" or type(y) ~= "number" or type(z) ~= "number") then
980 fail_coordinates()
981 return
983 tree_pos = {x=x, y=y, z=z}
984 elseif(fields.plantmode == "Relative coordinates") then
985 if(type(x)~="number" or type(y) ~= "number" or type(z) ~= "number") then
986 fail_coordinates()
987 return
989 tree_pos = player:getpos()
990 tree_pos.x = tree_pos.x + x
991 tree_pos.y = tree_pos.y + y
992 tree_pos.z = tree_pos.z + z
993 elseif(fields.plantmode == "Distance in viewing direction") then
994 if(type(distance)~="number") then
995 fail_distance()
996 return
998 tree_pos = vector.round(vector.add(player:getpos(), vector.multiply(player:get_look_dir(), distance)))
999 else
1000 minetest.log("error", "[ltool] fields.plantmode = "..tostring(fields.plantmode))
1003 if(tonumber(fields.seed)~=nil) then
1004 treedef.seed = tonumber(fields.seed)
1007 ltool.plant_tree(seltree_id, tree_pos)
1009 treedef.seed = nil
1011 elseif(fields.sapling) then
1012 if(seltree ~= nil) then
1013 if(privs.lplant ~= true) then
1014 ltool.save_fields(playername, formname, fields)
1015 local message = "You can't request saplings, you need to have the \"lplant\" privilege."
1016 ltool.show_dialog(playername, "ltool:treeform_error_sapling", message)
1017 return
1019 local seed = nil
1020 if(tonumber(fields.seed)~=nil) then
1021 seed = tonumber(fields.seed)
1023 local ret, ret2 = ltool.give_sapling(ltool.trees[seltree_id].treedef, seed, playername, true, ltool.trees[seltree_id].name)
1024 if(ret==false and ret2==2) then
1025 ltool.save_fields(playername, formname, fields)
1026 ltool.show_dialog(playername, "ltool:treeform_error_sapling", "Error: The sapling could not be given to you. Probably your inventory is full.")
1030 --[[ "Edit" tab ]]
1031 elseif(formname == "ltool:treeform_edit") then
1032 if(fields.edit_save or fields.edit_sapling) then
1033 local param1, param2
1034 param1, param2 = ltool.evaluate_edit_fields(fields, fields.edit_sapling ~= nil)
1035 if(fields.edit_save and privs.ledit ~= true) then
1036 ltool.save_fields(playername, formname, fields)
1037 local message = "You can't save trees, you need to have the \"ledit\" privilege."
1038 ltool.show_dialog(playername, "ltool:treeform_error_ledit", message)
1039 return
1041 if(fields.edit_sapling and privs.lplant ~= true) then
1042 ltool.save_fields(playername, formname, fields)
1043 local message = "You can't request saplings, you need to have the \"lplant\" privilege."
1044 ltool.show_dialog(playername, "ltool:treeform_error_ledit", message)
1045 return
1047 local tree_ok = true
1048 local treedef, name
1049 if(param1 ~= nil) then
1050 treedef = param1
1051 name = param2
1052 for k,v in pairs(ltool.trees) do
1053 if(fields.edit_save and v.name == name) then
1054 ltool.save_fields(playername, formname, fields)
1055 if(v.author == playername) then
1056 local formspec = "size[6,2;]label[0,0.2;You already have a tree with this name.\nDo you want to replace it?]"..
1057 "button[0,1.5;3,1;replace_yes;Yes]"..
1058 "button[3,1.5;3,1;replace_no;No]"
1059 minetest.show_formspec(playername, "ltool:treeform_replace", formspec)
1060 else
1061 ltool.show_dialog(playername, "ltool:treeform_error_nameclash", "Error: This name is already taken by someone else.\nPlease choose a different name.")
1063 return
1066 else
1067 tree_ok = false
1069 ltool.save_fields(playername, formname, fields)
1070 if(tree_ok == true) then
1071 if fields.edit_save then
1072 ltool.add_tree(name, playername, treedef)
1073 elseif fields.edit_sapling then
1074 local ret, ret2 = ltool.give_sapling(treedef, tostring(ltool.seed), playername, true, fields.name)
1075 if(ret==false and ret2==2) then
1076 ltool.save_fields(playername, formname, fields)
1077 ltool.show_dialog(playername, "ltool:treeform_error_sapling", "Error: The sapling could not be given to you. Probably your inventory is full.")
1080 else
1081 local message = "Error: The tree definition is invalid.\n"..
1082 minetest.formspec_escape(param2)
1083 ltool.show_dialog(playername, "ltool:treeform_error_badtreedef", message)
1086 if(fields.edit_clear) then
1087 local privs = minetest.get_player_privs(playername)
1088 ltool.save_fields(playername, formname, {})
1089 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(nil, privs.ledit, privs.lplant)
1090 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1092 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
1093 local fragment
1094 if(fields.edit_axiom) then
1095 fragment = "axiom;Axiom;"..s(fields.axiom)
1096 elseif(fields.edit_rules_a) then
1097 fragment = "rules_a;Rules set A;"..s(fields.rules_a)
1098 elseif(fields.edit_rules_b) then
1099 fragment = "rules_b;Rules set B;"..s(fields.rules_b)
1100 elseif(fields.edit_rules_c) then
1101 fragment = "rules_c;Rules set C;"..s(fields.rules_c)
1102 elseif(fields.edit_rules_d) then
1103 fragment = "rules_d;Rules set D;"..s(fields.rules_d)
1106 ltool.save_fields(playername, formname, fields)
1107 local formspec = ltool.formspec_editplus(fragment)
1108 minetest.show_formspec(playername, "ltool:treeform_editplus", formspec)
1110 if(mod_select_item and (fields.edit_trunk or fields.edit_leaves or fields.edit_leaves2 or fields.edit_fruit)) then
1111 ltool.save_fields(playername, formname, fields)
1112 select_item.show_dialog(playername, "ltool:node", function(itemstring)
1113 if itemstring ~= "air" and minetest.registered_nodes[itemstring] ~= nil then
1114 return true
1116 end)
1118 --[[ Larger edit fields for axiom and rules fields ]]
1119 elseif(formname == "ltool:treeform_editplus") then
1120 local editfields = ltool.playerinfos[playername].treeform.edit.fields
1121 local function addchar(c)
1122 local fragment
1123 if(c=="P") then c = "[" end
1124 if(c=="p") then c = "]" end
1125 if(fields.axiom) then
1126 fragment = "axiom;Axiom;"..s(fields.axiom..c)
1127 elseif(fields.rules_a) then
1128 fragment = "rules_a;Rules set A;"..s(fields.rules_a..c)
1129 elseif(fields.rules_b) then
1130 fragment = "rules_b;Rules set B;"..s(fields.rules_b..c)
1131 elseif(fields.rules_c) then
1132 fragment = "rules_c;Rules set C;"..s(fields.rules_c..c)
1133 elseif(fields.rules_d) then
1134 fragment = "rules_d;Rules set D;"..s(fields.rules_d..c)
1136 local formspec = ltool.formspec_editplus(fragment)
1137 minetest.show_formspec(playername, "ltool:treeform_editplus", formspec)
1139 if(fields.editplus_save) then
1140 local function o(writed, writer)
1141 if(writer~=nil) then
1142 return writer
1143 else
1144 return writed
1147 editfields.axiom = o(editfields.axiom, fields.axiom)
1148 editfields.rules_a = o(editfields.rules_a, fields.rules_a)
1149 editfields.rules_b = o(editfields.rules_b, fields.rules_b)
1150 editfields.rules_c = o(editfields.rules_c, fields.rules_c)
1151 editfields.rules_d = o(editfields.rules_d, fields.rules_d)
1152 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1153 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1154 elseif(fields.editplus_cancel or fields.quit) then
1155 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1156 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1157 else
1158 for id, field in pairs(fields) do
1159 if(string.sub(id,1,11) == "editplus_c_") then
1160 local char = string.sub(id,12,12)
1161 addchar(char)
1165 --[[ "Database" tab ]]
1166 elseif(formname == "ltool:treeform_database") then
1167 if(fields.treelist) then
1168 local event = minetest.explode_textlist_event(fields.treelist)
1169 if(event.type == "CHG") then
1170 ltool.playerinfos[playername].dbsel = event.index
1171 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(event.index, playername)
1172 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1174 elseif(fields.database_copy) then
1175 if(seltree ~= nil) then
1176 if(ltool.playerinfos[playername] ~= nil) then
1177 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.tree_to_fields(seltree), privs.ledit, privs.lplant)
1178 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1180 else
1181 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", "Error: No tree is selected.")
1183 elseif(fields.database_update) then
1184 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1185 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1187 elseif(fields.database_delete) then
1188 if(privs.ledit ~= true) then
1189 ltool.save_fields(playername, formname, fields)
1190 local message = "You can't delete trees, you need to have the \"ledit\" privilege."
1191 ltool.show_dialog(playername, "ltool:treeform_error_ledit_db", message)
1192 return
1194 if(seltree ~= nil) then
1195 if(playername == seltree.author) then
1196 local remove_id = ltool.get_selected_tree_id(playername)
1197 if(remove_id ~= nil) then
1198 ltool.remove_tree(remove_id)
1199 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1200 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1202 else
1203 ltool.show_dialog(playername, "ltool:treeform_error_delete", "Error: This tree is not your own. You may only delete your own trees.")
1205 else
1206 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", "Error: No tree is selected.")
1208 elseif(fields.database_rename) then
1209 if(seltree ~= nil) then
1210 if(privs.ledit ~= true) then
1211 ltool.save_fields(playername, formname, fields)
1212 local message = "You can't rename trees, you need to have the \"ledit\" privilege."
1213 ltool.show_dialog(playername, "ltool:treeform_error_ledit_db", message)
1214 return
1216 if(playername == seltree.author) then
1217 local formspec = "field[newname;New name:;"..minetest.formspec_escape(seltree.name).."]"
1218 minetest.show_formspec(playername, "ltool:treeform_rename", formspec)
1219 else
1220 ltool.show_dialog(playername, "ltool:treeform_error_rename_forbidden", "Error: This tree is not your own. You may only rename your own trees.")
1222 else
1223 ltool.show_dialog(playername, "ltool:treeform_error_nodbsel", "Error: No tree is selected.")
1226 --[[ Process "Do you want to replace this tree?" dialog ]]
1227 elseif(formname == "ltool:treeform_replace") then
1228 local editfields = ltool.playerinfos[playername].treeform.edit.fields
1229 local newtreedef, newname = ltool.evaluate_edit_fields(editfields)
1230 if(privs.ledit ~= true) then
1231 local message = "You can't overwrite trees, you need to have the \"ledit\" privilege."
1232 minetest.show_dialog(playername, "ltool:treeform_error_ledit", message)
1233 return
1235 if(fields.replace_yes) then
1236 for tree_id,tree in pairs(ltool.trees) do
1237 if(tree.name == newname) then
1238 --[[ The old tree is deleted and a
1239 new one with a new ID is created ]]
1240 local new_tree_id = ltool.next_tree_id
1241 ltool.trees[new_tree_id] = {}
1242 ltool.trees[new_tree_id].treedef = newtreedef
1243 ltool.trees[new_tree_id].name = newname
1244 ltool.trees[new_tree_id].author = tree.author
1245 ltool.next_tree_id = ltool.next_tree_id + 1
1246 ltool.trees[tree_id] = nil
1247 ltool.playerinfos[playername].dbsel = ltool.number_of_trees
1251 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(editfields, privs.ledit, privs.lplant)
1252 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1253 elseif(formname == "ltool:treeform_help") then
1254 local tab = tonumber(fields.ltool_help_tab)
1255 if(tab ~= nil) then
1256 ltool.playerinfos[playername].treeform.help.tab = tab
1257 local formspec = ltool.formspec_size..ltool.formspec_header(4)..ltool.tab_help(tab)
1258 minetest.show_formspec(playername, "ltool:treeform_help", formspec)
1260 if(fields.create_template) then
1261 local newfields = {
1262 axiom="FFFFFAFFBF",
1263 rules_a="[&&&FFFFF&&FFFF][&&&++++FFFFF&&FFFF][&&&----FFFFF&&FFFF]",
1264 rules_b="[&&&++FFFFF&&FFFF][&&&--FFFFF&&FFFF][&&&------FFFFF&&FFFF]",
1265 trunk="mapgen_tree",
1266 leaves="mapgen_leaves",
1267 angle="30",
1268 iterations="2",
1269 random_level="0",
1270 trunk_type="single",
1271 thin_branches="true",
1272 fruit_chance="10",
1273 fruit="mapgen_apple",
1274 name = "Example Tree "..ltool.next_tree_id
1276 ltool.save_fields(playername, formname, newfields)
1277 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(newfields, privs.ledit, privs.lplant)
1278 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1280 --[[ Tree renaming dialog ]]
1281 elseif(formname == "ltool:treeform_rename") then
1282 if(privs.ledit ~= true) then
1283 ltool.save_fields(playername, formname, fields)
1284 local message = "You can't delete trees, you need to have the \"ledit\" privilege."
1285 ltool.show_dialog(playername, "ltool:treeform_error_ledit_delete", message)
1286 return
1288 if(fields.newname ~= "" and fields.newname ~= nil) then
1289 ltool.rename_tree(ltool.get_selected_tree_id(playername), fields.newname)
1290 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1291 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1292 else
1293 ltool.show_dialog(playername, "ltool:treeform_error_bad_rename", "Error: This name is empty. The tree name must be non-empty.")
1295 --[[ Here come various error messages to handle ]]
1296 elseif(formname == "ltool:treeform_error_badtreedef" or formname == "ltool:treeform_error_nameclash" or formname == "ltool:treeform_error_ledit") then
1297 local formspec = ltool.formspec_size..ltool.formspec_header(1)..ltool.tab_edit(ltool.playerinfos[playername].treeform.edit.fields, privs.ledit, privs.lplant)
1298 minetest.show_formspec(playername, "ltool:treeform_edit", formspec)
1299 elseif(formname == "ltool:treeform_error_badplantfields" or formname == "ltool:treeform_error_sapling" or formname == "ltool:treeform_error_lplant") then
1300 local formspec = ltool.formspec_size..ltool.formspec_header(3)..ltool.tab_plant(seltree, ltool.playerinfos[playername].treeform.plant.fields, privs.lplant)
1301 minetest.show_formspec(playername, "ltool:treeform_plant", formspec)
1302 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
1303 local formspec = ltool.formspec_size..ltool.formspec_header(2)..ltool.tab_database(ltool.playerinfos[playername].dbsel, playername)
1304 minetest.show_formspec(playername, "ltool:treeform_database", formspec)
1305 elseif(formname == "ltool:treeform_error_bad_rename") then
1306 local formspec = "field[newname;New name:;"..minetest.formspec_escape(seltree.name).."]"
1307 minetest.show_formspec(playername, "ltool:treeform_rename", formspec)
1308 else
1309 -- Action for Inventory++ button
1310 if fields.ltool and minetest.get_modpath("inventory_plus") then
1311 ltool.show_treeform(playername)
1312 return
1317 if mod_select_item then
1318 select_item.register_on_select_item(function(playername, dialogname, itemstring)
1319 if dialogname == "ltool:node" then
1320 if itemstring then
1321 local f = ltool.playerinfos[playername].treeform.edit.fields
1322 if f.edit_trunk then
1323 f.trunk = itemstring
1324 elseif f.edit_leaves then
1325 f.leaves = itemstring
1326 elseif f.edit_leaves2 then
1327 f.leaves2 = itemstring
1328 elseif f.edit_fruit then
1329 f.fruit = itemstring
1332 ltool.show_treeform(playername)
1333 return false
1335 end)
1338 --[[ These 2 functions are basically just table initializions and cleanups ]]
1339 function ltool.leave(player)
1340 ltool.playerinfos[player:get_player_name()] = nil
1343 function ltool.join(player)
1344 local infotable = {}
1345 infotable.dbsel = nil
1346 infotable.treeform = {}
1347 infotable.treeform.database = {}
1348 --[[ This table stores a mapping of the textlist IDs in the database formspec and the tree IDs.
1349 It is updated each time ltool.tab_database is called. ]]
1350 infotable.treeform.database.textlist = nil
1351 --[[ the “fields” tables store the values of the input fields of a formspec. It is updated
1352 whenever the formspec is changed, i.e. on tab change ]]
1353 infotable.treeform.database.fields = {}
1354 infotable.treeform.plant = {}
1355 infotable.treeform.plant.fields = {}
1356 infotable.treeform.edit = {}
1357 infotable.treeform.edit.fields = {}
1358 infotable.treeform.help = {}
1359 infotable.treeform.help.tab = 1
1360 ltool.playerinfos[player:get_player_name()] = infotable
1362 -- Add Inventory++ support
1363 if minetest.get_modpath("inventory_plus") then
1364 inventory_plus.register_button(player, "ltool", "L-System Tree Utility")
1368 function ltool.save_to_file()
1369 local savetable = {}
1370 savetable.trees = ltool.trees
1371 savetable.number_of_trees = ltool.number_of_trees
1372 savetable.next_tree_id = ltool.next_tree_id
1373 local savestring = minetest.serialize(savetable)
1374 local filepath = minetest.get_worldpath().."/ltool.mt"
1375 local file = io.open(filepath, "w")
1376 if(file) then
1377 file:write(savestring)
1378 io.close(file)
1379 minetest.log("action", "[ltool] Tree data saved to "..filepath..".")
1380 else
1381 minetest.log("error", "[ltool] Failed to write ltool data to "..filepath".")
1386 minetest.register_on_player_receive_fields(ltool.process_form)
1388 minetest.register_on_leaveplayer(ltool.leave)
1390 minetest.register_on_joinplayer(ltool.join)
1392 minetest.register_on_shutdown(ltool.save_to_file)
1394 local button_action = function(player)
1395 ltool.show_treeform(player:get_player_name())
1398 if minetest.get_modpath("unified_inventory") ~= nil then
1399 unified_inventory.register_button("ltool", {
1400 type = "image",
1401 image = "ltool_sapling.png",
1402 tooltip = "L-System Tree Utility",
1403 action = button_action,
1407 if minetest.get_modpath("sfinv_buttons") ~= nil then
1408 sfinv_buttons.register_button("ltool", {
1409 title = "L-System Tree Utility",
1410 tooltip = "Invent your own trees and plant them",
1411 image = "ltool_sapling.png",
1412 action = button_action,