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