summaryrefslogtreecommitdiff
path: root/biome_lib
diff options
context:
space:
mode:
Diffstat (limited to 'biome_lib')
-rw-r--r--biome_lib/API.txt579
-rw-r--r--biome_lib/README.md30
-rw-r--r--biome_lib/depends.txt3
-rw-r--r--biome_lib/init.lua737
-rw-r--r--biome_lib/locale/de.txt5
-rw-r--r--biome_lib/locale/fr.txt5
-rw-r--r--biome_lib/locale/template.txt5
-rw-r--r--biome_lib/locale/tr.txt5
8 files changed, 1369 insertions, 0 deletions
diff --git a/biome_lib/API.txt b/biome_lib/API.txt
new file mode 100644
index 0000000..73e310f
--- /dev/null
+++ b/biome_lib/API.txt
@@ -0,0 +1,579 @@
+This document describes the Plantlife mod API.
+
+Last revision: 2015-02-16
+
+
+=========
+Functions
+=========
+
+There are three main functions defined by the main "biome_lib" mod:
+
+spawn_on_surfaces()
+register_generate_plant()
+grow_plants()
+
+There are also several internal, helper functions that can be called if so
+desired, but they are not really intended for use by other mods and may change
+at any time. They are briefly described below these main functions, but see
+init.lua for details.
+
+Most functions in plants lib are declared locally to avoid namespace
+collisions with other mods. They are accessible via the "biome_lib" method,
+e.g. biome_lib:spawn_on_surfaces() and so forth.
+
+=====
+spawn_on_surfaces(biome)
+spawn_on_surfaces(sdelay, splant, sradius, schance, ssurface, savoid)
+
+This first function is an ABM-based spawner function originally created as
+part of Ironzorg's flowers mod. It has since been largely extended and
+expanded. There are two ways to call this function: You can either pass it
+several individual string and number parameters to use the legacy interface,
+or you can pass a single biome definition as a table, with all of your options
+spelled out nicely. This is the preferred method.
+
+When used with the legacy interface, you must specify the parameters exactly
+in order, with the first five being mandatory (even if some are set to nil),
+and the last one being optional:
+
+sdelay: The value passed to the ABM's interval parameter, in seconds.
+splant: The node name of the item to spawn (e.g.
+ "flowers:flower_rose"). A plant will of course only be
+ spawned if the node about to be replaced is air.
+sradius: Don't spawn within this many nodes of the avoid items
+ mentioned below. If set to nil, this check is skipped.
+schance: The value passed to the ABM's chance parameter, normally in
+ the 10-100 range (1-in-X chance of operating on a given node)
+ssurface: String with the name of the node on which to spawn the plant
+ in question, such as "default:sand" or
+ "default:dirt_with_grass". It is not recommended to put air,
+ stone, or plain dirt here if you can use some other node, as
+ doing so will cause the engine to process potentially large
+ numbers of such nodes when deciding when to execute the ABM
+ and where it should operate.
+savoid: Table with a list of groups and/or node names to avoid when
+ spawning the plant, such as {"group:flowers", "default:tree"}.
+
+When passed a table as the argument, and thus using the modern calling method,
+you must pass a number of arguments in the form of an ordinary keyed-value
+table. Below is a list of everything supported by this function:
+
+biome = {
+ spawn_plants = something, -- [*] String or table; see below.
+ spawn_delay = number, -- same as sdelay, above.
+ spawn_chance = number, -- same as schance, above.
+ spawn_surfaces = {table}, -- List of node names on which the plants
+ -- should be spawned. As with the single-node "ssurface"
+ -- option in the legacy API, you should not put stone, air,
+ -- etc. here.
+
+ ---- From here down are a number of optional parameters. You will
+ ---- most likely want to use at least some of these to limit how and
+ ---- where your objects are spawned.
+
+ avoid_nodes = {table}, -- same meaning as savoid, above
+ avoid_radius = num, -- same as sradius
+ seed_diff = num, -- The Perlin seed difference value passed to the
+ -- minetest.get_perlin() function. Used along with
+ -- the global Perlin controls below to create the
+ -- "biome" in which the plants will spawn. Defaults
+ -- to 0 if not provided.
+ light_min = num, -- Minimum amount of light necessary to make a plant
+ -- spawn. Defaults to 0.
+ light_max = num, -- Maximum amount of light needed to spawn. Defaults
+ -- to the engine's MAX_LIGHT value of 14.
+ neighbors = {table}, -- List of neighboring nodes that need to be
+ -- immediately next to the node the plant is about to
+ -- spawn on. Can also be a string with a single node
+ -- name. It is both passed to the ABM as the
+ -- "neighbors" parameter, and is used to manually
+ -- check the adjacent nodes. It only takes one of
+ -- these for the spawn routine to mark the target as
+ -- spawnable. Defaults to nil (ignored).
+ ncount = num, -- There must be at least this many of the above
+ -- neighbors in the eight spaces immediately
+ -- surrounding the node the plant is about to spawn on
+ -- for it to happen. If not provided, this check is
+ -- disabled.
+ facedir = num, -- The value passed to the param2 variable when adding
+ -- the node to the map. Defaults to 0. Be sure that
+ -- the value you use here (and the range thereof) is
+ -- appropriate for the type of node you're spawning.
+ random_facedir = {table}, -- If set, the table should contain two values.
+ -- If they're both provided, the spawned plant will be
+ -- given a random facedir value in the range specified
+ -- by these two numbers. Overrides the facedir
+ -- parameter above, if it exists. Use {0,3} if you
+ -- want the full range for wallmounted nodes, or {2,5}
+ -- for most everything else, or any other pair of
+ -- numbers appropriate for the node you want to spawn.
+ depth_max = num, -- If the object spawns on top of a water source, the
+ -- water must be at most this deep. Defaults to 1.
+ min_elevation = num, -- Surface must be at this altitude or higher to
+ -- spawn at all. Defaults to -31000...
+ max_elevation = num, -- ...but must be no higher than this altitude.
+ -- Defaults to +31000.
+ near_nodes = {table}, -- List of nodes that must be somewhere in the
+ -- vicinity in order for the plant to spawn. Can also
+ -- be a string with a single node name. If not
+ -- provided, this check is disabled.
+ near_nodes_size = num, -- How large of an area to check for the above
+ -- node. Specifically, this checks a flat, horizontal
+ -- area centered on the node to be spawned on.
+ -- Defaults to 0, but is ignored if the above
+ -- near_nodes value is not set.
+ near_nodes_vertical = num, -- Used with the size value above, this extends
+ -- the vertical range of the near nodes search.
+ -- Basically, this turns the flat region described
+ -- above into a cuboid region. The area to be checked
+ -- will extend this high and this low above/below the
+ -- target node, centered thereon. Defaults to 1 (only
+ -- check the layer above, the layer at, and the layer
+ -- below the target node), but is ignored if
+ -- near_nodes is not set.
+ near_nodes_count = num, -- How many of the above nodes must be within that
+ -- radius. Defaults to 1 but is ignored if near_nodes
+ -- isn't set. Bear in mind that the total area to be
+ -- checked is equal to:
+ -- (near_nodes_size^2)*near_nodes_vertical*2
+ -- For example, if size is 10 and vertical is 4, then
+ -- the area is (10^2)*8 = 800 nodes in size, so you'll
+ -- want to make sure you specify a value appropriate
+ -- for the size of the area being tested.
+ air_size = num, -- How large of an area to check for air above and
+ -- around the target. If omitted, only the space
+ -- above the target is checked. This does not check
+ -- for air at the sides or below the target.
+ air_count = num, -- How many of the surrounding nodes need to be air
+ -- for the above check to return true. If omitted,
+ -- only the space above the target is checked.
+ plantlife_limit = num, -- The value compared against the generic "plants
+ -- can grow here" Perlin noise layer. Smaller numbers
+ -- result in more abundant plants. Range of -1 to +1,
+ -- with values in the range of about 0 to 0.5 being
+ -- most useful. Defaults to 0.1.
+ temp_min = num, -- Minimum temperature needed for the desired object
+ -- to spawn. This is a 2d Perlin value, which has an
+ -- inverted range of +1 to -1. Larger values
+ -- represent *colder* temperatures, so this value is
+ -- actually the upper end of the desired Perlin range.
+ -- See the temperature map section at the bottom of
+ -- this document for details on how these values work.
+ -- Defaults to +1 (unlimited coldness).
+ temp_max = num, -- Maximum temperature/lower end of the Perlin range.
+ -- Defaults to -1 (unlimited heat).
+ humidity_min = num, -- Minimum humidity for the plant to spawn in. Like
+ -- the temperature map, this is a Perlin value where
+ -- lower numbers mean more humidity in the area.
+ -- Defaults to +1 (0% humidity).
+ humidity_max = num, -- Maximum humidity for the plant to spawn at.
+ -- Defaults to -1 (100% humidity).
+ verticals_list = {table}, -- List of nodes that should be considered to be
+ -- natural walls.
+ alt_wallnode = "string", -- If specified, this node will be substituted in
+ -- place of the plant(s) defined by spawn_plants
+ -- above, if the spawn target has one or more adjacent
+ -- walls. In such a case, the two above facedir
+ -- parameters will be ignored.
+ spawn_on_side = bool, -- Set this to true to immediately spawn the node on
+ -- one side of the target node rather than the top.
+ -- The code will search for an airspace to the side of
+ -- the target, then spawn the plant at the first one
+ -- found. The above facedir and random_facedir
+ -- parameters are ignored in this case. If the above
+ -- parameters for selecting generic wall nodes are
+ -- provided, this option is ignored. Important note:
+ -- the facedir values assigned by this option only
+ -- make sense with wallmounted nodes (nodes which
+ -- don't use facedir won't be affected).
+ choose_random_wall = bool, -- if set to true, and searching for walls is
+ -- being done, just pick any random wall if there is
+ -- one, rather than returning the first one.
+ spawn_on_bottom = bool, -- If set to true, spawn the object below the
+ -- target node instead of above it. The above
+ -- spawn_on_side variable takes precedence over this
+ -- one if both happen to be true. When using this
+ -- option with the random facedir function above, the
+ -- values given to the facedir parameter are for
+ -- regular nodes, not wallmounted.
+ spawn_replace_node = bool, -- If set to true, the target node itself is
+ -- replaced by the spawned object. Overrides the
+ -- spawn_on_bottom and spawn_on_side settings.
+}
+
+[*] spawn_plants must be either a table or a string. If it's a table, the
+values therein are treated as a list of nodenames to pick from randomly on
+each application of the ABM code. The more nodes you can pack into this
+parameter to avoid making too many calls to this function, the lower the CPU
+load will likely be.
+
+You can also specify a string containing the name of a function to execute.
+In this case, the function will be passed a single position parameter
+indicating where the function should place the desired object, and the checks
+for spawning on top vs. sides vs. bottom vs. replacing the target node will be
+skipped.
+
+By default, if a biome node, size, and count are not defined, the biome
+checking is disabled. Same holds true for the nneighbors bit above that.
+
+
+=====
+biome_lib:register_generate_plant(biome, nodes_or_function_or_treedef)
+
+To register an object to be spawned at mapgen time rather than via an ABM,
+call this function with two parameters: a table with your object's biome
+information, and a string, function, or table describing what to do if the
+engine finds a suitable surface node (see below).
+
+The biome table contains quite a number of options, though there are fewer
+here than are available in the ABM-based spawner, as some stuff doesn't make
+sense at map-generation time.
+
+biome = {
+ surface = something, -- What node(s). May be a string such as
+ -- "default:dirt_with_grass" or a table with
+ -- multiple such entries.
+
+ ---- Everything else is optional, but you'll definitely want to use
+ ---- some of these other fields to limit where and under what
+ ---- conditions the objects are spawned.
+
+ below_nodes = {table}, -- List of nodes that must be below the target
+ -- node. Useful in snow biomes to keep objects from
+ -- spawning in snow that's on the wrong surface for
+ -- that object.
+ avoid_nodes = {table}, -- List of nodes to avoid when spawning. Groups are
+ -- not supported here.
+ avoid_radius = num, -- How much distance to leave between the object to be
+ -- added and the objects to be avoided. If this or
+ -- the avoid_nodes value is nil/omitted, this check is
+ -- skipped. Avoid using excessively large radii.
+ rarity = num, -- How rare should this object be in its biome? Larger
+ -- values make objects more rare, via:
+ -- math.random(1,100) > this
+ max_count = num, -- The absolute maximum number of your object that
+ -- should be allowed to spawn in a 5x5x5 mapblock area
+ -- (80x80x80 nodes). Defaults to 5, but be sure you
+ -- set this to some reasonable value depending on your
+ -- object and its size if 5 is insufficient.
+ seed_diff = num, -- Perlin seed-diff value. Defaults to 0, which
+ -- causes the function to inherit the global value of
+ -- 329.
+ neighbors = {table}, -- What ground nodes must be right next to and at the
+ -- same elevation as the node to be spawned on.
+ ncount = num, -- At least this many of the above nodes must be next
+ -- to the node to spawn on. Any value greater than 8
+ -- will probably cause the code to never spawn
+ -- anything. Defaults to 0.
+ depth = num, -- How deep/thick of a layer the spawned-on node must
+ -- be. Typically used for water.
+ min_elevation = num, -- Minimum elevation in meters/nodes. Defaults to
+ -- -31000 (unlimited).
+ max_elevation = num, -- Max elevation. Defaults to +31000 (unlimited).
+ near_nodes = {table}, -- what nodes must be in the general vicinity of the
+ -- object being spawned.
+ near_nodes_size = num, -- how wide of a search area to look for the nodes
+ -- in that list.
+ near_nodes_vertical = num, -- How high/low of an area to search from the
+ -- target node.
+ near_nodes_count = num, -- at least this many of those nodes must be in
+ -- the area.
+ plantlife_limit = num, -- The value compared against the generic "plants
+ -- can grow here" Perlin noise layer. Smaller numbers
+ -- result in more abundant plants. Range of -1 to +1,
+ -- with values in the range of about 0 to 0.5 being
+ -- most useful. Defaults to 0.1.
+ temp_min = num, -- Coldest allowable temperature for a plant to spawn
+ -- (that is, the largest Perlin value).
+ temp_max = num, -- warmest allowable temperature to spawn a plant
+ -- (lowest Perlin value).
+ verticals_list = {table}, -- Same as with the spawn_on_surfaces function.
+ check_air = bool, -- Flag to tell the mapgen code to check for air above
+ -- the spawn target. Defaults to true if not
+ -- explicitly set to false. Set this to false VERY
+ -- SPARINGLY, as it will slow the map generator down.
+ delete_above = bool, -- Flag to tell the mapgen code to delete the two
+ -- nodes directly above the spawn target just before
+ -- adding the plant or tree. Useful when generating
+ -- in snow biomes. Defaults to false.
+ delete_above_surround = bool, -- Flag to tell the mapgen code to also
+ -- delete the five nodes surrounding the above space,
+ -- and the five nodes above those, resulting in a two-
+ -- node-deep cross-shaped empty region above/around
+ -- the spawn target. Useful when adding trees to snow
+ -- biomes. Defaults to false.
+ spawn_replace_node = bool, -- same as with the ABM spawner.
+ random_facedir = {table}, -- same as with the ABM spawner.
+}
+
+Regarding nodes_or_function_or_treedef, this must either be a string naming
+a node to spawn, a table with a list of nodes to choose from, a table with an
+L-Systems tree definition, or a function.
+
+If you specified a string, the code will attempt to determine whether that
+string specifies a valid node name. If it does, that node will be placed on
+top of the target position directly (unless one of the other mapgen options
+directs the code to do otherwise).
+
+If you specified a table and there is no "axiom" field, the code assumes that
+it is a list of nodes. Simply name one node per entry in the list, e.g.
+{"default:junglegrass", "default:dry_shrub"} and so on, for as many nodes as
+you want to list. A random node from the list will be chosen each time the
+code goes to place a node.
+
+If you specified a table, and there *is* an "axiom" field, the code assumes
+that this table contains an L-Systems tree definition, which will be passed
+directly to the engine's spawn_tree() function along with the position on
+which to spawn the tree.
+
+You can also supply a function to be directly executed, which is given the
+current node position (the usual "pos" table format) as its sole argument. It
+will be called in the form:
+
+ somefunction(pos)
+
+
+=====
+biome_lib:grow_plants(options)
+
+The third function, grow_plants() is used to turn the spawned nodes above
+into something else over time. This function has no return value, and accepts
+a biome definition table as the only parameter. These are defined like so:
+
+options = {
+ grow_plant = "string", -- Name of the node to be grown into something
+ -- else. This value is passed to the ABM as the
+ -- "nodenames" parameter, so it is the plants
+ -- themselves that are the ABM trigger, rather than
+ -- the ground they spawned on. A plant will only grow
+ -- if the node above it is air. Can also be a table,
+ -- but note that all nodes referenced therein will be
+ -- grown into the same object.
+ grow_delay = num, -- Passed as the ABM "interval" parameter, as with
+ -- spawning.
+ grow_chance = num, -- Passed as the ABM "chance" parameter.
+ grow_result = "string", -- Name of the node into which the grow_plant
+ -- node(s) should transform when the ABM executes.
+
+ ---- Everything from here down is optional.
+
+ dry_early_node = "string", -- This value is ignored except for jungle
+ -- grass (a corner case needed by that mod), where it
+ -- indicates which node the grass must be on in order
+ -- for it to turn from the short size to
+ -- "default:dry_shrub" instead of the medium size.
+ grow_nodes = {table}, -- One of these nodes must be under the plant in
+ -- order for it to grow at all. Normally this should
+ -- be the same as the list of surfaces passed to the
+ -- spawning ABM as the "nodenames" parameter. This is
+ -- so that the plant can be manually placed on
+ -- something like a flower pot or something without it
+ -- necessarily growing and perhaps dieing. Defaults
+ -- to "default:dirt_with_grass".
+ facedir = num, -- Same as with spawning a plant.
+ need_wall = bool, -- Set this to true if you the plant needs to grow
+ -- against a wall. Defaults to false.
+ verticals_list = {table}, -- same as with spawning a plant.
+ choose_random_wall = bool, -- same as with spawning a plant.
+ grow_vertically = bool, -- Set this to true if the plant needs to grow
+ -- vertically, as in climbing poison ivy. Defaults to
+ -- false.
+ height_limit = num, -- Set this to limit how tall the desired node can
+ -- grow. The mod will search straight down from the
+ -- position being spawned at to find a ground node,
+ -- set via the field below. Defaults to 5 nodes.
+ ground_nodes = {table}, -- What nodes should be treated as "the ground"
+ -- below a vertically-growing plant. Usually this
+ -- should be the same as the grow_nodes table, but
+ -- might also include, for example, water or some
+ -- other surrounding material. Defaults to
+ -- "default:dirt_with_grass".
+ grow_function = something, -- [*] see below.
+ seed_diff = num, -- [*] see below.
+}
+
+[*] grow_function can take one of three possible settings: it can be nil (or
+ not provided), a string, or a table.
+
+If it is not provided or it's set to nil, all of the regular growing code is
+executed normally, the value of seed_diff, if any, is ignored, and the node to
+be placed is assumed to be specified in the grow_result variable.
+
+If this value is set to a simple string, this is treated as the name of the
+function to use to grow the plant. In this case, all of the usual growing
+code is executeed, but then instead of a plant being simply added to the
+world, grow_result is ignored and the named function is executed and passed a
+few parmeters in the following general form:
+
+ somefunction(pos, perlin1, perlin2)
+
+These values represent the current position (the usual table), the Perlin
+noise value for that spot in the generic "plants can grow here" map for the
+seed_diff value above, the Perlin value for that same spot from the
+temperature map, and the detected neighboring wall face, if there was one (or
+nil if not). If seed_diff is not provided, it defaults to 0.
+
+If this variable is instead set to a table, it is treated an an L-Systems tree
+definition. All of the growing code is executed in the usual manner, then the
+tree described by that definition is spawned at the current position instead,
+and grow_result is ignored.
+
+
+=====
+find_adjacent_wall(pos, verticals, randomflag)
+
+Of the few helper functions, this one expects a position parameter and a table
+with the list of nodes that should be considered as walls. The code will
+search around the given position for a neighboring wall, returning the first
+one it finds as a facedir value, or nil if there are no adjacent walls.
+
+If randomflag is set to true, the function will just return the facedir of any
+random wall it finds adjacent to the target position. Defaults to false if
+not specified.
+
+=====
+is_node_loaded(pos)
+
+This acts as a wrapper for the minetest.get_node_or_nil(node_pos)
+function and accepts a single position parameter. Returns true if the node in
+question is already loaded, or false if not.
+
+
+=====
+dbg(string)
+
+This is a simple debug output function which takes one string parameter. It
+just checks if DEBUG is true and outputs the phrase "[Plantlife] " followed by
+the supplied string, via the print() function, if so.
+
+=====
+biome_lib:generate_tree(pos, treemodel)
+biome_lib:grow_tree(pos, treemodel)
+
+In the case of the growing code and the mapgen-based tree generator code,
+generating a tree is done via the above two calls, which in turn immediately
+call the usual spawn_tree() functions. This rerouting exists as a way for
+other mods to hook into biome_lib's tree-growing functions in general,
+perhaps to execute something extra whenever a tree is spawned.
+
+biome_lib:generate_tree(pos, treemodel) is called any time a tree is spawned
+at map generation time. 'pos' is the position of the block on which the tree
+is to be placed. 'treemodel' is the standard L-Systems tree definition table
+expected by the spawn_tree() function. Refer to the 'trunk' field in that
+table to derive the name of the tree being spawned.
+
+biome_lib:grow_tree(pos, treemodel) does the same sort of thing whenever a
+tree is spawned within the abm-based growing code, for example when growing a
+sapling into a tree.
+
+
+=====
+There are other, internal helper functions that are not meant for use by other
+mods. Don't rely on them, as they are subject to change without notice.
+
+
+===============
+Global Settings
+===============
+
+Set this to true if you want the mod to spam your console with debug info :-)
+
+ plantlife_debug = false
+
+
+======================
+Fertile Ground Mapping
+======================
+
+The mod uses Perlin noise to create "biomes" of the various plants, via the
+minetest.get_perlin() function. At present, there are three layers of
+Perlin noise used.
+
+The first one is for a "fertile ground" layer, which I tend to refer to as the
+generic "stuff can potentially grow here" layer. Its values are hard-coded:
+
+ biome_lib.plantlife_seed_diff = 329
+ perlin_octaves = 3
+ perlin_persistence = 0.6
+ perlin_scale = 100
+
+For more information on how Perlin noise is generated, you will need to search
+the web, as these default values were from that which is used by minetest_game
+to spawn jungle grass at mapgen time, and I'm still learning how Perlin noise
+works. ;-)
+
+
+===================
+Temperature Mapping
+===================
+
+The second Perlin layer is a temperature map, with values taken from
+SPlizard's Snow Biomes mod so that the two will be compatible, since that mod
+appears to be the standard now. Those values are:
+
+ temperature_seeddiff = 112
+ temperature_octaves = 3
+ temperature_persistence = 0.5
+ temperature_scale = 150
+
+The way Perlin values are used by this mod, in keeping with the snow mod's
+apparent methods, larger values returned by the Perlin function represent
+*colder* temperatures. In this mod, the following table gives a rough
+approximation of how temperature maps to these values, normalized to
+0.53 = 0 °C and +1.0 = -25 °C.
+
+Perlin Approx. Temperature
+-1.0 81 °C ( 178 °F)
+-0.75 68 °C ( 155 °F)
+-0.56 58 °C ( 136 °F)
+-0.5 55 °C ( 131 °F)
+-0.25 41 °C ( 107 °F)
+-0.18 38 °C ( 100 °F)
+ 0 28 °C ( 83 °F)
+ 0.13 21 °C ( 70 °F)
+ 0.25 15 °C ( 59 °F)
+ 0.5 2 °C ( 35 °F)
+ 0.53 0 °C ( 32 °F)
+ 0.75 -12 °C ( 11 °F)
+ 0.86 -18 °C ( 0 °F)
+ 1.0 -25 °C (- 13 °F)
+
+Included in this table are even 0.25 steps in Perlin values along with some
+common temperatures on both the Centigrade and Fahrenheit scales. Note that
+unless you're trying to model the Moon or perhaps Mercury in your mods/maps,
+you probably won't need to bother with Perlin values of less than -0.56 or so.
+
+
+================
+Humidity Mapping
+================
+
+Last but not least is a moisture/humidity map. Like the temperature map
+above, Perlin values can be tested to determine the approximate humidity of
+the *air* in the area. This humidity map is basically the perlin layer used
+for deserts.
+
+A value of +1.0 is very moist (basically a thick fog, if it could be seen), a
+value of roughly +0.25 represents the edge of a desert as usually seen in the
+game, and a value of -1.0 is as dry as a bone.
+
+This does not check for nearby water, just general air humidity, and that
+being the case, nearby ground does not affect the reported humidity of a
+region (because this isn't yet possible to calculate yet). Use the near_nodes
+and avoid_nodes parameters and their related options to check for water and
+such.
+
+The Perlin values use for this layer are:
+
+ humidity_seeddiff = 9130
+ humidity_octaves = 3
+ humidity_persistence = 0.5
+ humidity_scale = 250
+
+And this particular one is mapped slightly differently from the others:
+
+ noise3 = perlin3:get2d({x=p_top.x+150, y=p_top.z+50})
+
+(Note the +150 and +50 offsets)
+
diff --git a/biome_lib/README.md b/biome_lib/README.md
new file mode 100644
index 0000000..ce539d1
--- /dev/null
+++ b/biome_lib/README.md
@@ -0,0 +1,30 @@
+# Biome Lib
+
+This library's purpose is to allow other mods to add growing things to the map in a straightforward, simple manner. It contains all the core functions needed by mods and modpacks such as More Trees, Tiny Trees, Plantlife, and others.
+
+Spawning of plants is optionally sensitive to the amount of available light, elevation, nearness to other nodes, plant-to-plant density, water depth, and a whole host of controls.
+
+All objects spawned or generated using this mod use Perlin noise to stay within simple biomes, rather than just letting everything just spread around the map randomly.
+
+This library also features a basic temperature map, which should blend in nicely with SPlizard's Snow Biomes mod (the same Perlin settings are used, with the assumption that the edge of a snow biome is 0° Centigrade).
+
+Both mapgen-based spawning and ABM-based spawning is supported. Growing code is strictly ABM-based. L-system trees can be spawned at mapgen time via the engine's spawn_tree() function and are quite fast.
+
+It is primarily intended for mapgen v6, but it should work fine when used with mapgen v7.
+
+**Dependencies**: default from minetest_game
+
+**Recommends**: [Plantlife Modpack](https://github.com/VanessaE/plantlife_modpack),
+[More Trees](https://github.com/VanessaE/moretrees)
+
+**License**: WTFPL
+
+**API**: This mod supplies a small number of very powerful functions. They are, briefly:
+
+* biome_lib:register_generate_plant()
+* biome_lib:spawn_on_surfaces()
+* biome_lib:grow_plants()
+* biome_lib:find_valid_wall()
+* biome_lib:is_node_loaded()
+
+For a complete description of these functions as well as several of the internal variables within the mod, [read the API.txt document](https://raw.githubusercontent.com/VanessaE/biome_lib/master/API.txt) included in this package.
diff --git a/biome_lib/depends.txt b/biome_lib/depends.txt
new file mode 100644
index 0000000..c48fe0d
--- /dev/null
+++ b/biome_lib/depends.txt
@@ -0,0 +1,3 @@
+default
+intllib?
+
diff --git a/biome_lib/init.lua b/biome_lib/init.lua
new file mode 100644
index 0000000..01a17d2
--- /dev/null
+++ b/biome_lib/init.lua
@@ -0,0 +1,737 @@
+-- Plantlife library mod by Vanessa Ezekowitz
+--
+-- License: WTFPL
+--
+-- I got the temperature map idea from "hmmmm", values used for it came from
+-- Splizard's snow mod.
+--
+
+-- Various settings - most of these probably won't need to be changed
+
+biome_lib = {}
+
+plantslib = setmetatable({}, { __index=function(t,k) print("Use of deprecated function:", k) return biome_lib[k] end })
+
+biome_lib.blocklist_aircheck = {}
+biome_lib.blocklist_no_aircheck = {}
+
+biome_lib.surface_nodes_aircheck = {}
+biome_lib.surface_nodes_no_aircheck = {}
+
+biome_lib.surfaceslist_aircheck = {}
+biome_lib.surfaceslist_no_aircheck = {}
+
+biome_lib.actioncount_aircheck = {}
+biome_lib.actioncount_no_aircheck = {}
+
+biome_lib.actionslist_aircheck = {}
+biome_lib.actionslist_no_aircheck = {}
+
+biome_lib.modpath = minetest.get_modpath("biome_lib")
+
+biome_lib.total_no_aircheck_calls = 0
+
+-- Boilerplate to support localized strings if intllib mod is installed.
+local S
+if minetest.get_modpath("intllib") then
+ S = intllib.Getter()
+else
+ S = function(s) return s end
+end
+biome_lib.intllib = S
+
+local DEBUG = false --... except if you want to spam the console with debugging info :-)
+
+function biome_lib:dbg(msg)
+ if DEBUG then
+ print("[Plantlife] "..msg)
+ minetest.log("verbose", "[Plantlife] "..msg)
+ end
+end
+
+biome_lib.plantlife_seed_diff = 329 -- needs to be global so other mods can see it
+
+local perlin_octaves = 3
+local perlin_persistence = 0.6
+local perlin_scale = 100
+
+local temperature_seeddiff = 112
+local temperature_octaves = 3
+local temperature_persistence = 0.5
+local temperature_scale = 150
+
+local humidity_seeddiff = 9130
+local humidity_octaves = 3
+local humidity_persistence = 0.5
+local humidity_scale = 250
+
+local time_scale = 1
+local time_speed = tonumber(minetest.setting_get("time_speed"))
+
+if time_speed and time_speed > 0 then
+ time_scale = 72 / time_speed
+end
+
+--PerlinNoise(seed, octaves, persistence, scale)
+
+biome_lib.perlin_temperature = PerlinNoise(temperature_seeddiff, temperature_octaves, temperature_persistence, temperature_scale)
+biome_lib.perlin_humidity = PerlinNoise(humidity_seeddiff, humidity_octaves, humidity_persistence, humidity_scale)
+
+-- Local functions
+
+function biome_lib:is_node_loaded(node_pos)
+ local n = minetest.get_node_or_nil(node_pos)
+ if (not n) or (n.name == "ignore") then
+ return false
+ end
+ return true
+end
+
+function biome_lib:set_defaults(biome)
+ biome.seed_diff = biome.seed_diff or 0
+ biome.min_elevation = biome.min_elevation or -31000
+ biome.max_elevation = biome.max_elevation or 31000
+ biome.temp_min = biome.temp_min or 1
+ biome.temp_max = biome.temp_max or -1
+ biome.humidity_min = biome.humidity_min or 1
+ biome.humidity_max = biome.humidity_max or -1
+ biome.plantlife_limit = biome.plantlife_limit or 0.1
+ biome.near_nodes_vertical = biome.near_nodes_vertical or 1
+
+-- specific to on-generate
+
+ biome.neighbors = biome.neighbors or biome.surface
+ biome.near_nodes_size = biome.near_nodes_size or 0
+ biome.near_nodes_count = biome.near_nodes_count or 1
+ biome.rarity = biome.rarity or 50
+ biome.max_count = biome.max_count or 5
+ if biome.check_air ~= false then biome.check_air = true end
+
+-- specific to abm spawner
+ biome.seed_diff = biome.seed_diff or 0
+ biome.light_min = biome.light_min or 0
+ biome.light_max = biome.light_max or 15
+ biome.depth_max = biome.depth_max or 1
+ biome.facedir = biome.facedir or 0
+end
+
+local function search_table(t, s)
+ for i = 1, #t do
+ if t[i] == s then return true end
+ end
+ return false
+end
+
+-- register the list of surfaces to spawn stuff on, filtering out all duplicates.
+-- separate the items by air-checking or non-air-checking map eval methods
+
+function biome_lib:register_generate_plant(biomedef, nodes_or_function_or_model)
+
+ -- if calling code passes an undefined node for a surface or
+ -- as a node to be spawned, don't register an action for it.
+
+ if type(nodes_or_function_or_model) == "string"
+ and string.find(nodes_or_function_or_model, ":")
+ and not minetest.registered_nodes[nodes_or_function_or_model] then
+ biome_lib:dbg("Warning: Ignored registration for undefined spawn node: "..dump(nodes_or_function_or_model))
+ return
+ end
+
+ if type(nodes_or_function_or_model) == "string"
+ and not string.find(nodes_or_function_or_model, ":") then
+ biome_lib:dbg("Warning: Registered function call using deprecated string method: "..dump(nodes_or_function_or_model))
+ end
+
+ if biomedef.check_air == false then
+ biome_lib:dbg("Register no-air-check mapgen hook: "..dump(nodes_or_function_or_model))
+ biome_lib.actionslist_no_aircheck[#biome_lib.actionslist_no_aircheck + 1] = { biomedef, nodes_or_function_or_model }
+ local s = biomedef.surface
+ if type(s) == "string" then
+ if s and (string.find(s, "^group:") or minetest.registered_nodes[s]) then
+ if not search_table(biome_lib.surfaceslist_no_aircheck, s) then
+ biome_lib.surfaceslist_no_aircheck[#biome_lib.surfaceslist_no_aircheck + 1] = s
+ end
+ else
+ biome_lib:dbg("Warning: Ignored no-air-check registration for undefined surface node: "..dump(s))
+ end
+ else
+ for i = 1, #biomedef.surface do
+ local s = biomedef.surface[i]
+ if s and (string.find(s, "^group:") or minetest.registered_nodes[s]) then
+ if not search_table(biome_lib.surfaceslist_no_aircheck, s) then
+ biome_lib.surfaceslist_no_aircheck[#biome_lib.surfaceslist_no_aircheck + 1] = s
+ end
+ else
+ biome_lib:dbg("Warning: Ignored no-air-check registration for undefined surface node: "..dump(s))
+ end
+ end
+ end
+ else
+ biome_lib:dbg("Register with-air-checking mapgen hook: "..dump(nodes_or_function_or_model))
+ biome_lib.actionslist_aircheck[#biome_lib.actionslist_aircheck + 1] = { biomedef, nodes_or_function_or_model }
+ local s = biomedef.surface
+ if type(s) == "string" then
+ if s and (string.find(s, "^group:") or minetest.registered_nodes[s]) then
+ if not search_table(biome_lib.surfaceslist_aircheck, s) then
+ biome_lib.surfaceslist_aircheck[#biome_lib.surfaceslist_aircheck + 1] = s
+ end
+ else
+ biome_lib:dbg("Warning: Ignored with-air-checking registration for undefined surface node: "..dump(s))
+ end
+ else
+ for i = 1, #biomedef.surface do
+ local s = biomedef.surface[i]
+ if s and (string.find(s, "^group:") or minetest.registered_nodes[s]) then
+ if not search_table(biome_lib.surfaceslist_aircheck, s) then
+ biome_lib.surfaceslist_aircheck[#biome_lib.surfaceslist_aircheck + 1] = s
+ end
+ else
+ biome_lib:dbg("Warning: Ignored with-air-checking registration for undefined surface node: "..dump(s))
+ end
+ end
+ end
+ end
+end
+
+function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes, checkair)
+
+ biome_lib:set_defaults(biome)
+
+ -- filter stage 1 - find nodes from the supplied surfaces that are within the current biome.
+
+ local in_biome_nodes = {}
+ local perlin_fertile_area = minetest.get_perlin(biome.seed_diff, perlin_octaves, perlin_persistence, perlin_scale)
+
+ for i = 1, #snodes do
+ local pos = snodes[i]
+ local p_top = { x = pos.x, y = pos.y + 1, z = pos.z }
+ local noise1 = perlin_fertile_area:get2d({x=pos.x, y=pos.z})
+ local noise2 = biome_lib.perlin_temperature:get2d({x=pos.x, y=pos.z})
+ local noise3 = biome_lib.perlin_humidity:get2d({x=pos.x+150, y=pos.z+50})
+ local biome_surfaces_string = dump(biome.surface)
+ local surface_ok = false
+
+ if not biome.depth then
+ local dest_node = minetest.get_node(pos)
+ if string.find(biome_surfaces_string, dest_node.name) then
+ surface_ok = true
+ else
+ if string.find(biome_surfaces_string, "group:") then
+ for j = 1, #biome.surface do
+ if string.find(biome.surface[j], "^group:")
+ and minetest.get_item_group(dest_node.name, biome.surface[j]) then
+ surface_ok = true
+ break
+ end
+ end
+ end
+ end
+ elseif not string.find(biome_surfaces_string, minetest.get_node({ x = pos.x, y = pos.y-biome.depth-1, z = pos.z }).name) then
+ surface_ok = true
+ end
+
+ if surface_ok
+ and (not checkair or minetest.get_node(p_top).name == "air")
+ and pos.y >= biome.min_elevation
+ and pos.y <= biome.max_elevation
+ and noise1 > biome.plantlife_limit
+ and noise2 <= biome.temp_min
+ and noise2 >= biome.temp_max
+ and noise3 <= biome.humidity_min
+ and noise3 >= biome.humidity_max
+ and (not biome.ncount or #(minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, biome.neighbors)) > biome.ncount)
+ and (not biome.near_nodes or #(minetest.find_nodes_in_area({x=pos.x-biome.near_nodes_size, y=pos.y-biome.near_nodes_vertical, z=pos.z-biome.near_nodes_size}, {x=pos.x+biome.near_nodes_size, y=pos.y+biome.near_nodes_vertical, z=pos.z+biome.near_nodes_size}, biome.near_nodes)) >= biome.near_nodes_count)
+ and math.random(1,100) > biome.rarity
+ and (not biome.below_nodes or string.find(dump(biome.below_nodes), minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name) )
+ then
+ in_biome_nodes[#in_biome_nodes + 1] = pos
+ end
+ end
+
+ -- filter stage 2 - find places within that biome area to place the plants.
+
+ local num_in_biome_nodes = #in_biome_nodes
+
+ if num_in_biome_nodes > 0 then
+ for i = 1, math.min(biome.max_count, num_in_biome_nodes) do
+ local tries = 0
+ local spawned = false
+ while tries < 2 and not spawned do
+ local pos = in_biome_nodes[math.random(1, num_in_biome_nodes)]
+ if biome.spawn_replace_node then
+ pos.y = pos.y-1
+ end
+ local p_top = { x = pos.x, y = pos.y + 1, z = pos.z }
+
+ if not (biome.avoid_nodes and biome.avoid_radius and minetest.find_node_near(p_top, biome.avoid_radius + math.random(-1.5,2), biome.avoid_nodes)) then
+ if biome.delete_above then
+ minetest.remove_node(p_top)
+ minetest.remove_node({x=p_top.x, y=p_top.y+1, z=p_top.z})
+ end
+
+ if biome.delete_above_surround then
+ minetest.remove_node({x=p_top.x-1, y=p_top.y, z=p_top.z})
+ minetest.remove_node({x=p_top.x+1, y=p_top.y, z=p_top.z})
+ minetest.remove_node({x=p_top.x, y=p_top.y, z=p_top.z-1})
+ minetest.remove_node({x=p_top.x, y=p_top.y, z=p_top.z+1})
+
+ minetest.remove_node({x=p_top.x-1, y=p_top.y+1, z=p_top.z})
+ minetest.remove_node({x=p_top.x+1, y=p_top.y+1, z=p_top.z})
+ minetest.remove_node({x=p_top.x, y=p_top.y+1, z=p_top.z-1})
+ minetest.remove_node({x=p_top.x, y=p_top.y+1, z=p_top.z+1})
+ end
+
+ if biome.spawn_replace_node then
+ minetest.remove_node(pos)
+ end
+
+ local objtype = type(nodes_or_function_or_model)
+
+ if objtype == "table" then
+ if nodes_or_function_or_model.axiom then
+ biome_lib:generate_tree(pos, nodes_or_function_or_model)
+ spawned = true
+ else
+ local fdir = nil
+ if biome.random_facedir then
+ fdir = math.random(biome.random_facedir[1], biome.random_facedir[2])
+ end
+ minetest.set_node(p_top, { name = nodes_or_function_or_model[math.random(#nodes_or_function_or_model)], param2 = fdir })
+ spawned = true
+ end
+ elseif objtype == "string" and
+ minetest.registered_nodes[nodes_or_function_or_model] then
+ local fdir = nil
+ if biome.random_facedir then
+ fdir = math.random(biome.random_facedir[1], biome.random_facedir[2])
+ end
+ minetest.set_node(p_top, { name = nodes_or_function_or_model, param2 = fdir })
+ spawned = true
+ elseif objtype == "function" then
+ nodes_or_function_or_model(pos)
+ spawned = true
+ elseif objtype == "string" and pcall(loadstring(("return %s(...)"):
+ format(nodes_or_function_or_model)),pos) then
+ spawned = true
+ else
+ biome_lib:dbg("Warning: Ignored invalid definition for object "..dump(nodes_or_function_or_model).." that was pointed at {"..dump(pos).."}")
+ end
+ else
+ tries = tries + 1
+ end
+ end
+ end
+ end
+end
+
+-- Primary mapgen spawner, for mods that can work with air checking enabled on
+-- a surface during the initial map read stage.
+
+function biome_lib:generate_block_with_air_checking()
+ if #biome_lib.blocklist_aircheck > 0 then
+
+ local minp = biome_lib.blocklist_aircheck[1][1]
+ local maxp = biome_lib.blocklist_aircheck[1][2]
+
+ -- use the block hash as a unique key into the surface nodes
+ -- tables, so that we can write the tables thread-safely.
+
+ local blockhash = minetest.hash_node_position(minp)
+
+ if not biome_lib.surface_nodes_aircheck.blockhash then
+
+ if type(minetest.find_nodes_in_area_under_air) == "function" then -- use newer API call
+ biome_lib.surface_nodes_aircheck.blockhash =
+ minetest.find_nodes_in_area_under_air(minp, maxp, biome_lib.surfaceslist_aircheck)
+ else
+ local search_area = minetest.find_nodes_in_area(minp, maxp, biome_lib.surfaceslist_aircheck)
+
+ -- search the generated block for air-bounded surfaces the slow way.
+
+ biome_lib.surface_nodes_aircheck.blockhash = {}
+
+ for i = 1, #search_area do
+ local pos = search_area[i]
+ local p_top = { x=pos.x, y=pos.y+1, z=pos.z }
+ if minetest.get_node(p_top).name == "air" then
+ biome_lib.surface_nodes_aircheck.blockhash[#biome_lib.surface_nodes_aircheck.blockhash + 1] = pos
+ end
+ end
+ end
+ biome_lib.actioncount_aircheck.blockhash = 1
+
+ else
+ if biome_lib.actioncount_aircheck.blockhash <= #biome_lib.actionslist_aircheck then
+ -- [1] is biome, [2] is node/function/model
+ biome_lib:populate_surfaces(
+ biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash][1],
+ biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash][2],
+ biome_lib.surface_nodes_aircheck.blockhash, true)
+ biome_lib.actioncount_aircheck.blockhash = biome_lib.actioncount_aircheck.blockhash + 1
+ else
+ if biome_lib.surface_nodes_aircheck.blockhash then
+ table.remove(biome_lib.blocklist_aircheck, 1)
+ biome_lib.surface_nodes_aircheck.blockhash = nil
+ end
+ end
+ end
+ end
+end
+
+-- Secondary mapgen spawner, for mods that require disabling of
+-- checking for air during the initial map read stage.
+
+function biome_lib:generate_block_no_aircheck()
+ if #biome_lib.blocklist_no_aircheck > 0 then
+
+ local minp = biome_lib.blocklist_no_aircheck[1][1]
+ local maxp = biome_lib.blocklist_no_aircheck[1][2]
+
+ local blockhash = minetest.hash_node_position(minp)
+
+ if not biome_lib.surface_nodes_no_aircheck.blockhash then
+
+ -- directly read the block to be searched into the chunk cache
+
+ biome_lib.surface_nodes_no_aircheck.blockhash =
+ minetest.find_nodes_in_area(minp, maxp, biome_lib.surfaceslist_no_aircheck)
+ biome_lib.actioncount_no_aircheck.blockhash = 1
+
+ else
+ if biome_lib.actioncount_no_aircheck.blockhash <= #biome_lib.actionslist_no_aircheck then
+ biome_lib:populate_surfaces(
+ biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash][1],
+ biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash][2],
+ biome_lib.surface_nodes_no_aircheck.blockhash, false)
+ biome_lib.actioncount_no_aircheck.blockhash = biome_lib.actioncount_no_aircheck.blockhash + 1
+ else
+ if biome_lib.surface_nodes_no_aircheck.blockhash then
+ table.remove(biome_lib.blocklist_no_aircheck, 1)
+ biome_lib.surface_nodes_no_aircheck.blockhash = nil
+ end
+ end
+ end
+ end
+end
+
+-- "Record" the chunks being generated by the core mapgen
+
+minetest.register_on_generated(function(minp, maxp, blockseed)
+ biome_lib.blocklist_aircheck[#biome_lib.blocklist_aircheck + 1] = { minp, maxp }
+end)
+
+minetest.register_on_generated(function(minp, maxp, blockseed)
+ biome_lib.blocklist_no_aircheck[#biome_lib.blocklist_no_aircheck + 1] = { minp, maxp }
+end)
+
+-- "Play" them back, populating them with new stuff in the process
+
+minetest.register_globalstep(function(dtime)
+ if dtime < 0.2 and -- don't attempt to populate if lag is already too high
+ (#biome_lib.blocklist_aircheck > 0 or #biome_lib.blocklist_no_aircheck > 0) then
+ biome_lib.globalstep_start_time = minetest.get_us_time()
+ biome_lib.globalstep_runtime = 0
+ while (#biome_lib.blocklist_aircheck > 0 or #biome_lib.blocklist_no_aircheck > 0)
+ and biome_lib.globalstep_runtime < 200000 do -- 0.2 seconds, in uS.
+ if #biome_lib.blocklist_aircheck > 0 then
+ biome_lib:generate_block_with_air_checking()
+ end
+ if #biome_lib.blocklist_no_aircheck > 0 then
+ biome_lib:generate_block_no_aircheck()
+ end
+ biome_lib.globalstep_runtime = minetest.get_us_time() - biome_lib.globalstep_start_time
+ end
+ end
+end)
+
+-- Play out the entire log all at once on shutdown
+-- to prevent unpopulated map areas
+
+minetest.register_on_shutdown(function()
+ print("[biome_lib] Stand by, playing out the rest of the aircheck mapblock log")
+ print("(there are "..#biome_lib.blocklist_aircheck.." entries)...")
+ while true do
+ biome_lib:generate_block_with_air_checking(0.1)
+ if #biome_lib.blocklist_aircheck == 0 then return end
+ end
+end)
+
+minetest.register_on_shutdown(function()
+ print("[biome_lib] Stand by, playing out the rest of the no-aircheck mapblock log")
+ print("(there are "..#biome_lib.blocklist_aircheck.." entries)...")
+ while true do
+ biome_lib:generate_block_no_aircheck(0.1)
+ if #biome_lib.blocklist_no_aircheck == 0 then return end
+ end
+end)
+
+-- The spawning ABM
+
+function biome_lib:spawn_on_surfaces(sd,sp,sr,sc,ss,sa)
+
+ local biome = {}
+
+ if type(sd) ~= "table" then
+ biome.spawn_delay = sd -- old api expects ABM interval param here.
+ biome.spawn_plants = {sp}
+ biome.avoid_radius = sr
+ biome.spawn_chance = sc
+ biome.spawn_surfaces = {ss}
+ biome.avoid_nodes = sa
+ else
+ biome = sd
+ end
+
+ if biome.spawn_delay*time_scale >= 1 then
+ biome.interval = biome.spawn_delay*time_scale
+ else
+ biome.interval = 1
+ end
+
+ biome_lib:set_defaults(biome)
+ biome.spawn_plants_count = #(biome.spawn_plants)
+
+ minetest.register_abm({
+ nodenames = biome.spawn_surfaces,
+ interval = biome.interval,
+ chance = biome.spawn_chance,
+ neighbors = biome.neighbors,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ local p_top = { x = pos.x, y = pos.y + 1, z = pos.z }
+ local n_top = minetest.get_node(p_top)
+ local perlin_fertile_area = minetest.get_perlin(biome.seed_diff, perlin_octaves, perlin_persistence, perlin_scale)
+ local noise1 = perlin_fertile_area:get2d({x=p_top.x, y=p_top.z})
+ local noise2 = biome_lib.perlin_temperature:get2d({x=p_top.x, y=p_top.z})
+ local noise3 = biome_lib.perlin_humidity:get2d({x=p_top.x+150, y=p_top.z+50})
+ if noise1 > biome.plantlife_limit
+ and noise2 <= biome.temp_min
+ and noise2 >= biome.temp_max
+ and noise3 <= biome.humidity_min
+ and noise3 >= biome.humidity_max
+ and biome_lib:is_node_loaded(p_top) then
+ local n_light = minetest.get_node_light(p_top, nil)
+ if not (biome.avoid_nodes and biome.avoid_radius and minetest.find_node_near(p_top, biome.avoid_radius + math.random(-1.5,2), biome.avoid_nodes))
+ and n_light >= biome.light_min
+ and n_light <= biome.light_max
+ and (not(biome.neighbors and biome.ncount) or #(minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, biome.neighbors)) > biome.ncount )
+ and (not(biome.near_nodes and biome.near_nodes_count and biome.near_nodes_size) or #(minetest.find_nodes_in_area({x=pos.x-biome.near_nodes_size, y=pos.y-biome.near_nodes_vertical, z=pos.z-biome.near_nodes_size}, {x=pos.x+biome.near_nodes_size, y=pos.y+biome.near_nodes_vertical, z=pos.z+biome.near_nodes_size}, biome.near_nodes)) >= biome.near_nodes_count)
+ and (not(biome.air_count and biome.air_size) or #(minetest.find_nodes_in_area({x=p_top.x-biome.air_size, y=p_top.y, z=p_top.z-biome.air_size}, {x=p_top.x+biome.air_size, y=p_top.y, z=p_top.z+biome.air_size}, "air")) >= biome.air_count)
+ and pos.y >= biome.min_elevation
+ and pos.y <= biome.max_elevation
+ then
+ local walldir = biome_lib:find_adjacent_wall(p_top, biome.verticals_list, biome.choose_random_wall)
+ if biome.alt_wallnode and walldir then
+ if n_top.name == "air" then
+ minetest.set_node(p_top, { name = biome.alt_wallnode, param2 = walldir })
+ end
+ else
+ local currentsurface = minetest.get_node(pos).name
+ if currentsurface ~= "default:water_source"
+ or (currentsurface == "default:water_source" and #(minetest.find_nodes_in_area({x=pos.x, y=pos.y-biome.depth_max-1, z=pos.z}, {x=pos.x, y=pos.y, z=pos.z}, {"default:dirt", "default:dirt_with_grass", "default:sand"})) > 0 )
+ then
+ local rnd = math.random(1, biome.spawn_plants_count)
+ local plant_to_spawn = biome.spawn_plants[rnd]
+ local fdir = biome.facedir
+ if biome.random_facedir then
+ fdir = math.random(biome.random_facedir[1],biome.random_facedir[2])
+ end
+ if type(biome.spawn_plants) == "string" then
+ assert(loadstring(biome.spawn_plants.."(...)"))(pos)
+ elseif not biome.spawn_on_side and not biome.spawn_on_bottom and not biome.spawn_replace_node then
+ if n_top.name == "air" then
+ minetest.set_node(p_top, { name = plant_to_spawn, param2 = fdir })
+ end
+ elseif biome.spawn_replace_node then
+ minetest.set_node(pos, { name = plant_to_spawn, param2 = fdir })
+
+ elseif biome.spawn_on_side then
+ local onside = biome_lib:find_open_side(pos)
+ if onside then
+ minetest.set_node(onside.newpos, { name = plant_to_spawn, param2 = onside.facedir })
+ end
+ elseif biome.spawn_on_bottom then
+ if minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == "air" then
+ minetest.set_node({x=pos.x, y=pos.y-1, z=pos.z}, { name = plant_to_spawn, param2 = fdir} )
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ })
+end
+
+-- The growing ABM
+
+function biome_lib:grow_plants(opts)
+
+ local options = opts
+
+ options.height_limit = options.height_limit or 5
+ options.ground_nodes = options.ground_nodes or { "default:dirt_with_grass" }
+ options.grow_nodes = options.grow_nodes or { "default:dirt_with_grass" }
+ options.seed_diff = options.seed_diff or 0
+
+ if options.grow_delay*time_scale >= 1 then
+ options.interval = options.grow_delay*time_scale
+ else
+ options.interval = 1
+ end
+
+ minetest.register_abm({
+ nodenames = { options.grow_plant },
+ interval = options.interval,
+ chance = options.grow_chance,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ local p_top = {x=pos.x, y=pos.y+1, z=pos.z}
+ local p_bot = {x=pos.x, y=pos.y-1, z=pos.z}
+ local n_top = minetest.get_node(p_top)
+ local n_bot = minetest.get_node(p_bot)
+ local root_node = minetest.get_node({x=pos.x, y=pos.y-options.height_limit, z=pos.z})
+ local walldir = nil
+ if options.need_wall and options.verticals_list then
+ walldir = biome_lib:find_adjacent_wall(p_top, options.verticals_list, options.choose_random_wall)
+ end
+ if (n_top.name == "air" or n_top.name == "default:snow")
+ and (not options.need_wall or (options.need_wall and walldir)) then
+ -- corner case for changing short junglegrass
+ -- to dry shrub in desert
+ if n_bot.name == options.dry_early_node and options.grow_plant == "junglegrass:short" then
+ minetest.set_node(pos, { name = "default:dry_shrub" })
+
+ elseif options.grow_vertically and walldir then
+ if biome_lib:search_downward(pos, options.height_limit, options.ground_nodes) then
+ minetest.set_node(p_top, { name = options.grow_plant, param2 = walldir})
+ end
+
+ elseif not options.grow_result and not options.grow_function then
+ minetest.remove_node(pos)
+
+ else
+ biome_lib:replace_object(pos, options.grow_result, options.grow_function, options.facedir, options.seed_diff)
+ end
+ end
+ end
+ })
+end
+
+-- Function to decide how to replace a plant - either grow it, replace it with
+-- a tree, run a function, or die with an error.
+
+function biome_lib:replace_object(pos, replacement, grow_function, walldir, seeddiff)
+ local growtype = type(grow_function)
+ if growtype == "table" then
+ minetest.remove_node(pos)
+ biome_lib:grow_tree(pos, grow_function)
+ return
+ elseif growtype == "function" then
+ local perlin_fertile_area = minetest.get_perlin(seeddiff, perlin_octaves, perlin_persistence, perlin_scale)
+ local noise1 = perlin_fertile_area:get2d({x=pos.x, y=pos.z})
+ local noise2 = biome_lib.perlin_temperature:get2d({x=pos.x, y=pos.z})
+ grow_function(pos,noise1,noise2,walldir)
+ return
+ elseif growtype == "string" then
+ local perlin_fertile_area = minetest.get_perlin(seeddiff, perlin_octaves, perlin_persistence, perlin_scale)
+ local noise1 = perlin_fertile_area:get2d({x=pos.x, y=pos.z})
+ local noise2 = biome_lib.perlin_temperature:get2d({x=pos.x, y=pos.z})
+ assert(loadstring(grow_function.."(...)"))(pos,noise1,noise2,walldir)
+ return
+ elseif growtype == "nil" then
+ minetest.set_node(pos, { name = replacement, param2 = walldir})
+ return
+ elseif growtype ~= "nil" and growtype ~= "string" and growtype ~= "table" then
+ error("Invalid grow function "..dump(grow_function).." used on object at ("..dump(pos)..")")
+ end
+end
+
+-- function to decide if a node has a wall that's in verticals_list{}
+-- returns wall direction of valid node, or nil if invalid.
+
+function biome_lib:find_adjacent_wall(pos, verticals, randomflag)
+ local verts = dump(verticals)
+ if randomflag then
+ local walltab = {}
+
+ if string.find(verts, minetest.get_node({ x=pos.x-1, y=pos.y, z=pos.z }).name) then walltab[#walltab + 1] = 3 end
+ if string.find(verts, minetest.get_node({ x=pos.x+1, y=pos.y, z=pos.z }).name) then walltab[#walltab + 1] = 2 end
+ if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z-1 }).name) then walltab[#walltab + 1] = 5 end
+ if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z+1 }).name) then walltab[#walltab + 1] = 4 end
+
+ if #walltab > 0 then return walltab[math.random(1, #walltab)] end
+
+ else
+ if string.find(verts, minetest.get_node({ x=pos.x-1, y=pos.y, z=pos.z }).name) then return 3 end
+ if string.find(verts, minetest.get_node({ x=pos.x+1, y=pos.y, z=pos.z }).name) then return 2 end
+ if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z-1 }).name) then return 5 end
+ if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z+1 }).name) then return 4 end
+ end
+ return nil
+end
+
+-- Function to search downward from the given position, looking for the first
+-- node that matches the ground table. Returns the new position, or nil if
+-- height limit is exceeded before finding it.
+
+function biome_lib:search_downward(pos, heightlimit, ground)
+ for i = 0, heightlimit do
+ if string.find(dump(ground), minetest.get_node({x=pos.x, y=pos.y-i, z = pos.z}).name) then
+ return {x=pos.x, y=pos.y-i, z = pos.z}
+ end
+ end
+ return false
+end
+
+function biome_lib:find_open_side(pos)
+ if minetest.get_node({ x=pos.x-1, y=pos.y, z=pos.z }).name == "air" then
+ return {newpos = { x=pos.x-1, y=pos.y, z=pos.z }, facedir = 2}
+ end
+ if minetest.get_node({ x=pos.x+1, y=pos.y, z=pos.z }).name == "air" then
+ return {newpos = { x=pos.x+1, y=pos.y, z=pos.z }, facedir = 3}
+ end
+ if minetest.get_node({ x=pos.x, y=pos.y, z=pos.z-1 }).name == "air" then
+ return {newpos = { x=pos.x, y=pos.y, z=pos.z-1 }, facedir = 4}
+ end
+ if minetest.get_node({ x=pos.x, y=pos.y, z=pos.z+1 }).name == "air" then
+ return {newpos = { x=pos.x, y=pos.y, z=pos.z+1 }, facedir = 5}
+ end
+ return nil
+end
+
+-- spawn_tree() on generate is routed through here so that other mods can hook
+-- into it.
+
+function biome_lib:generate_tree(pos, nodes_or_function_or_model)
+ minetest.spawn_tree(pos, nodes_or_function_or_model)
+end
+
+-- and this one's for the call used in the growing code
+
+function biome_lib:grow_tree(pos, nodes_or_function_or_model)
+ minetest.spawn_tree(pos, nodes_or_function_or_model)
+end
+
+-- Check for infinite stacks
+
+if minetest.get_modpath("unified_inventory") or not minetest.setting_getbool("creative_mode") then
+ biome_lib.expect_infinite_stacks = false
+else
+ biome_lib.expect_infinite_stacks = true
+end
+
+-- read a field from a node's definition
+
+function biome_lib:get_nodedef_field(nodename, fieldname)
+ if not minetest.registered_nodes[nodename] then
+ return nil
+ end
+ return minetest.registered_nodes[nodename][fieldname]
+end
+
+print("[Biome Lib] Loaded")
+
+minetest.after(0, function()
+ print("[Biome Lib] Registered a total of "..(#biome_lib.surfaceslist_aircheck)+(#biome_lib.surfaceslist_no_aircheck).." surface types to be evaluated, spread")
+ print("[Biome Lib] across "..#biome_lib.actionslist_aircheck.." actions with air-checking and "..#biome_lib.actionslist_no_aircheck.." actions without.")
+end)
+
diff --git a/biome_lib/locale/de.txt b/biome_lib/locale/de.txt
new file mode 100644
index 0000000..2886786
--- /dev/null
+++ b/biome_lib/locale/de.txt
@@ -0,0 +1,5 @@
+# Translation by Xanthin
+
+someone = jemand
+Sorry, %s owns that spot. = Entschuldige, %s gehoert diese Stelle.
+[Plantlife Library] Loaded = [Plantlife Library] Geladen
diff --git a/biome_lib/locale/fr.txt b/biome_lib/locale/fr.txt
new file mode 100644
index 0000000..9070900
--- /dev/null
+++ b/biome_lib/locale/fr.txt
@@ -0,0 +1,5 @@
+# Template
+
+someone = quelqu'un
+Sorry, %s owns that spot. = Désolé, %s possède cet endroit.
+[Plantlife Library] Loaded = [Librairie Plantlife] Chargée.
diff --git a/biome_lib/locale/template.txt b/biome_lib/locale/template.txt
new file mode 100644
index 0000000..0f5fbbd
--- /dev/null
+++ b/biome_lib/locale/template.txt
@@ -0,0 +1,5 @@
+# Template
+
+someone =
+Sorry, %s owns that spot. =
+[Plantlife Library] Loaded =
diff --git a/biome_lib/locale/tr.txt b/biome_lib/locale/tr.txt
new file mode 100644
index 0000000..4b596f4
--- /dev/null
+++ b/biome_lib/locale/tr.txt
@@ -0,0 +1,5 @@
+# Turkish translation by mahmutelmas06
+
+someone = birisi
+Sorry, %s owns that spot. = Üzgünüm, buranın sahibi %s.
+[Plantlife Library] Loaded = [Plantlife Library] yüklendi