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