1
0
mirror of https://github.com/mt-mods/biome_lib.git synced 2024-11-16 07:30:20 +01:00

Compare commits

..

No commits in common. "master" and "2021-01-30" have entirely different histories.

19 changed files with 849 additions and 1262 deletions

View File

@ -1,10 +0,0 @@
name: luacheck
on: [push, pull_request]
jobs:
luacheck:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Luacheck
uses: lunarmodules/luacheck@master

View File

@ -1,18 +0,0 @@
unused_args = false
globals = {
"biome_lib"
}
read_globals = {
-- Stdlib
string = {fields = {"split", "trim"}},
table = {fields = {"copy"}},
-- Minetest
"minetest", "vector",
"dump", "PerlinNoise",
-- mods
"default",
}

191
API.txt
View File

@ -1,32 +1,34 @@
This document describes the Plantlife mod API. This document describes the Plantlife mod API.
Last revision: 2021-04-20 Last revision: 2015-02-16
========= =========
Functions Functions
========= =========
There are two main functions defined by this mod: There are three main functions defined by the main "biome_lib" mod:
biome_lib.register_active_spawner() spawn_on_surfaces()
biome_lib.register_on_generate() register_generate_plant()
grow_plants()
There are also several internal, helper functions that can be called if so 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 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 at any time. They are briefly described below these main functions, but see
init.lua for details. init.lua for details.
Most functions in biome_lib are either declared locally or kept within its Most functions in plants lib are declared locally to avoid namespace
own namespace to avoid collisions/conflicts with other mods. collisions with other mods. They are accessible via the "biome_lib" method,
e.g. biome_lib:spawn_on_surfaces() and so forth.
===== =====
biome_lib.register_active_spawner(biome) spawn_on_surfaces(biome)
biome_lib.register_active_spawner(sdelay, splant, sradius, schance, ssurface, savoid) spawn_on_surfaces(sdelay, splant, sradius, schance, ssurface, savoid)
This first function is an ABM-based spawner function originally created as 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 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 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, 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 or you can pass a single biome definition as a table, with all of your options
spelled out nicely. This is the preferred method. spelled out nicely. This is the preferred method.
@ -110,9 +112,9 @@ biome = {
depth_max = num, -- If the object spawns on top of a water source, the depth_max = num, -- If the object spawns on top of a water source, the
-- water must be at most this deep. Defaults to 1. -- water must be at most this deep. Defaults to 1.
min_elevation = num, -- Surface must be at this altitude or higher to min_elevation = num, -- Surface must be at this altitude or higher to
-- spawn at all. Defaults to -16 meters. -- spawn at all. Defaults to -31000...
max_elevation = num, -- Surface must be no higher than this altitude. max_elevation = num, -- ...but must be no higher than this altitude.
-- Defaults to +48. -- Defaults to +31000.
near_nodes = {table}, -- List of nodes that must be somewhere in the near_nodes = {table}, -- List of nodes that must be somewhere in the
-- vicinity in order for the plant to spawn. Can also -- vicinity in order for the plant to spawn. Can also
-- be a string with a single node name. If not -- be a string with a single node name. If not
@ -135,7 +137,7 @@ biome = {
-- radius. Defaults to 1 but is ignored if near_nodes -- radius. Defaults to 1 but is ignored if near_nodes
-- isn't set. Bear in mind that the total area to be -- isn't set. Bear in mind that the total area to be
-- checked is equal to: -- checked is equal to:
-- (near_nodes_size^2)*near_nodes_vertical*2 -- (near_nodes_size^2)*near_nodes_vertical*2
-- For example, if size is 10 and vertical is 4, then -- For example, if size is 10 and vertical is 4, then
-- the area is (10^2)*8 = 800 nodes in size, so you'll -- the area is (10^2)*8 = 800 nodes in size, so you'll
-- want to make sure you specify a value appropriate -- want to make sure you specify a value appropriate
@ -202,7 +204,7 @@ biome = {
} }
[*] spawn_plants must be either a table or a string. If it's a table, the [*] 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 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 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 parameter to avoid making too many calls to this function, the lower the CPU
load will likely be. load will likely be.
@ -218,14 +220,14 @@ checking is disabled. Same holds true for the nneighbors bit above that.
===== =====
biome_lib.register_on_generate(biome, nodes_or_function_or_treedef) 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, 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 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 information, and a string, function, or table describing what to do if the
engine finds a suitable surface node (see below). engine finds a suitable surface node (see below).
The biome table contains quite a number of options, though there are fewer 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 here than are available in the ABM-based spawner, as some stuff doesn't make
sense at map-generation time. sense at map-generation time.
@ -250,21 +252,12 @@ biome = {
-- skipped. Avoid using excessively large radii. -- skipped. Avoid using excessively large radii.
rarity = num, -- How rare should this object be in its biome? Larger rarity = num, -- How rare should this object be in its biome? Larger
-- values make objects more rare, via: -- values make objects more rare, via:
-- math.random() * 100 > this -- math.random(1,100) > this
rarity_fertility -- The amount that the rarity is reduced by fertility.
= num, -- This makes the rarity field the upper bound for
-- rarity, and (rarity - rarity_fertility) the lower
-- bound. Defaults to 0.
max_count = num, -- The absolute maximum number of your object that max_count = num, -- The absolute maximum number of your object that
-- should be allowed to spawn in a 5x5x5 mapblock area -- should be allowed to spawn in a 5x5x5 mapblock area
-- (80x80x80 nodes). Defaults to 5, but be sure you -- (80x80x80 nodes). Defaults to 5, but be sure you
-- set this to some reasonable value depending on your -- set this to some reasonable value depending on your
-- object and its size if 5 is insufficient. -- object and its size if 5 is insufficient.
tries = num, -- the number of attempts that will be made to spawn
-- an object, defaults to 2. This means if the first
-- attempt fails due to something blocking the object
-- for example, another attempt will be made in
-- another random location.
seed_diff = num, -- Perlin seed-diff value. Defaults to 0, which seed_diff = num, -- Perlin seed-diff value. Defaults to 0, which
-- causes the function to inherit the global value of -- causes the function to inherit the global value of
-- 329. -- 329.
@ -277,8 +270,8 @@ biome = {
depth = num, -- How deep/thick of a layer the spawned-on node must depth = num, -- How deep/thick of a layer the spawned-on node must
-- be. Typically used for water. -- be. Typically used for water.
min_elevation = num, -- Minimum elevation in meters/nodes. Defaults to min_elevation = num, -- Minimum elevation in meters/nodes. Defaults to
-- -16 meters. -- -31000 (unlimited).
max_elevation = num, -- Max elevation. Defaults to +48m. max_elevation = num, -- Max elevation. Defaults to +31000 (unlimited).
near_nodes = {table}, -- what nodes must be in the general vicinity of the near_nodes = {table}, -- what nodes must be in the general vicinity of the
-- object being spawned. -- object being spawned.
near_nodes_size = num, -- how wide of a search area to look for the nodes near_nodes_size = num, -- how wide of a search area to look for the nodes
@ -341,17 +334,18 @@ will be called in the form:
somefunction(pos) somefunction(pos)
=====
biome_lib.update_plant(options)
This third function is used to turn the spawned nodes above into something =====
else over time. This function has no return value, and accepts a biome biome_lib:grow_plants(options)
definition table as the only parameter. These are defined like so:
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 = { options = {
label = string, -- set this to identify the ABM for Minetest's label = string, -- set this to identify the ABM for Minetest's
-- profiler. If not set, biome_lib will set it to -- profiler. If not set, biome_lib will set it to
-- "biome_lib.update_plant(): " appended with the node -- "biome_lib grow_plants(): " appended with the node
-- in grow_plant (or the first item if it's a table) -- in grow_plant (or the first item if it's a table)
grow_plant = "string" or {table}, -- Name(s) of the node(s) to be grown grow_plant = "string" or {table}, -- Name(s) of the node(s) to be grown
-- into something else. This value is passed to the -- into something else. This value is passed to the
@ -415,7 +409,7 @@ 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 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 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 world, grow_result is ignored and the named function is executed and passed a
few parmeters in the following general form: few parmeters in the following general form:
somefunction(pos, perlin1, perlin2) somefunction(pos, perlin1, perlin2)
@ -432,7 +426,7 @@ and grow_result is ignored.
===== =====
biome_lib.find_adjacent_wall(pos, verticals, randomflag) find_adjacent_wall(pos, verticals, randomflag)
Of the few helper functions, this one expects a position parameter and a table 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 with the list of nodes that should be considered as walls. The code will
@ -440,11 +434,11 @@ 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. 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 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 random wall it finds adjacent to the target position. Defaults to false if
not specified. not specified.
===== =====
biome_lib.is_node_loaded(pos) is_node_loaded(pos)
This acts as a wrapper for the minetest.get_node_or_nil(node_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 function and accepts a single position parameter. Returns true if the node in
@ -452,39 +446,29 @@ question is already loaded, or false if not.
===== =====
biome_lib.dbg(string, level) dbg(string)
This is a simple debug output function which takes one string parameter. It 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 just checks if DEBUG is true and outputs the phrase "[Plantlife] " followed by
the supplied string, via the print() function, if so. the supplied string, via the print() function, if so.
'level' is a number that, if supplied, dictates the lowest 'biome_lib_debug'
can be set to in minetest.conf for this message to be displayed. Both the
default log level and the default message level are 0, thus always showing the
supplied message.
Although it's not set in stone, a good practice is to use a level of 0 (or
just omit the value) for anything that generally important enough that it
ought always be shown, 1 for errors, 2 for warnings, 3 for info, 4 for verbose
spammy stuff.
===== =====
biome_lib.generate_ltree(pos, treemodel) biome_lib:generate_tree(pos, treemodel)
biome_lib.grow_ltree(pos, treemodel) biome_lib:grow_tree(pos, treemodel)
In the case of the growing code and the mapgen-based tree generator code, 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 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 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, other mods to hook into biome_lib's tree-growing functions in general,
perhaps to execute something extra whenever a tree is spawned. perhaps to execute something extra whenever a tree is spawned.
biome_lib.generate_ltree(pos, treemodel) is called any time 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 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 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 expected by the spawn_tree() function. Refer to the 'trunk' field in that
table to derive the name of the tree being spawned. table to derive the name of the tree being spawned.
biome_lib.grow_ltree(pos, treemodel) does the same sort of thing whenever a 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 tree is spawned within the abm-based growing code, for example when growing a
sapling into a tree. sapling into a tree.
@ -493,6 +477,22 @@ sapling into a tree.
There are other, internal helper functions that are not meant for use by other 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. 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
To slow down the playback of the queue (e.g. for really slow machines where
the 0.2 second max limiter isn't enough), set:
biome_lib_queue_run_ratio = <some value 1 to 100>
Default is 100 (basically percent of maximum runtime)
====================== ======================
Fertile Ground Mapping Fertile Ground Mapping
====================== ======================
@ -504,6 +504,7 @@ Perlin noise used.
The first one is for a "fertile ground" layer, which I tend to refer to as the 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: generic "stuff can potentially grow here" layer. Its values are hard-coded:
biome_lib.plantlife_seed_diff = 329
perlin_octaves = 3 perlin_octaves = 3
perlin_persistence = 0.6 perlin_persistence = 0.6
perlin_scale = 100 perlin_scale = 100
@ -527,7 +528,7 @@ appears to be the standard now. Those values are:
temperature_persistence = 0.5 temperature_persistence = 0.5
temperature_scale = 150 temperature_scale = 150
The way Perlin values are used by this mod, in keeping with the snow mod's 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 apparent methods, larger values returned by the Perlin function represent
*colder* temperatures. In this mod, the following table gives a rough *colder* temperatures. In this mod, the following table gives a rough
approximation of how temperature maps to these values, normalized to approximation of how temperature maps to these values, normalized to
@ -551,7 +552,7 @@ Perlin Approx. Temperature
Included in this table are even 0.25 steps in Perlin values along with some 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 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, 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. you probably won't need to bother with Perlin values of less than -0.56 or so.
@ -587,79 +588,3 @@ And this particular one is mapped slightly differently from the others:
(Note the +150 and +50 offsets) (Note the +150 and +50 offsets)
==================
Default game nodes
==================
Although this project was intended to be used with minetest_game, it can be
configured to work with something else instead. All you need to do is provide
the names of the nodes in your game you want biome_lib's internals to use.
See settingtypes.txt for a list. Any item listed there can be changed either
by adding it to your minetest.conf, or by using the "all settings" menu in
Minetest, whatever's appropriate for your particular setup.
==================
Engine Decorations
==================
If a call to biome_lib.register_on_generate() contains items and biome
definition settings that are suitable, biome_lib will pass that call on to the
engine instead, to use its built-in decorations feature, since it'll be much
faster than Lua.
For this to work, first the item to be added must either be a node, or a
table with a list of nodes that biome_lib would normally pick from randomly.
That is to say, you cannot specify an L-tree or a function here, as the engine
does not support that sort of thing (biome_lib will just switch to its normal
handling if you do).
Second, these biome definition items must not be present:
* below_nodes
* avoid_nodes
* avoid_radius
* neighbors
* ncount
* depth
* near_nodes_size
* near_nodes_vertical
* temp_min
* temp_max
* verticals_list
* delete_above
* delete_above_surround
The plantlife_limit definition item is ignored when checking if a particular
call can be routed to the engine.
The call given to the engine will use the remaining biome definition items in
the following manner:
deco_type = "simple",
flags = "all_floors"
decoration = node or table with node list
place_on = surface
y_min = min_elevation
y_max = max_elevation
spawn_by = near_nodes
num_spawn_by = near_nodes_count
param2 = \_ set to the range specified by the biome definition's
param2_max = / random_facedir table, if present, otherwise omitted
noise_params = {
octaves = biome_lib.fertile_perlin_octaves,
persist = biome_lib.fertile_perlin_persistence * (250/biome_lib.fertile_perlin_scale),
scale = ((100-biome.rarity)/100) * (math.min(biome.max_count, 6400)/6400),
seed = biome.seed_diff,
offset = 0,
spread = {x = 100, y = 100, z = 100},
lacunarity = 2,
flags = "absvalue"
}
If the biome definition's check_air setting is false, "force_placement" is
added to the decoration's flags setting.
If the biome def's spawn_replace_node is set to true, the decoration's
place_offset_y is set to -1 (otherwise it is omitted).

View File

@ -12,10 +12,10 @@ Both mapgen-based spawning and ABM-based spawning is supported. Growing code is
It is primarily intended for mapgen v6, but it should work fine when used with mapgen v7. It is primarily intended for mapgen v6, but it should work fine when used with mapgen v7.
**Dependencies:** nothing, but if you don't use `minetest_game`, you'll need to supply some settings (see API.txt). **Dependencies**: default from minetest_game
**Recommends**: [Plantlife Modpack](https://github.com/mt-mods/plantlife_modpack), **Recommends**: [Plantlife Modpack](https://github.com/minetest-mods/plantlife_modpack),
[More Trees](https://github.com/mt-mods/moretrees) [More Trees](https://github.com/minetest-mods/moretrees)
**API**: This mod supplies a small number of very powerful functions. They are, briefly: **API**: This mod supplies a small number of very powerful functions. They are, briefly:
@ -25,6 +25,4 @@ It is primarily intended for mapgen v6, but it should work fine when used with m
* biome_lib:find_valid_wall() * biome_lib:find_valid_wall()
* biome_lib:is_node_loaded() * biome_lib:is_node_loaded()
For a complete description of these functions as well as several of the internal variables within the mod, see `API.txt`. 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/minetest-mods/biome_lib/master/API.txt) included in this package.
**Configuration:** This mod has several variables you can set in your `minetest.conf` to change things a bit, from the default nodes it uses, to the debug log level and the block queue behavior. For a list with complete descriptions, see `settingtypes.txt`.

695
api.lua
View File

@ -1,695 +0,0 @@
biome_lib.block_log = {}
biome_lib.block_recheck_list = {}
biome_lib.run_block_recheck_list = false
biome_lib.actionslist_aircheck = {}
biome_lib.actionslist_no_aircheck = {}
biome_lib.surfaceslist_aircheck = {}
biome_lib.surfaceslist_no_aircheck = {}
biome_lib.registered_decorations = {}
biome_lib.fertile_perlin_octaves = 3
biome_lib.fertile_perlin_persistence = 0.6
biome_lib.fertile_perlin_scale = 100
local time_speed = tonumber(minetest.settings:get("time_speed"))
biome_lib.time_scale = 1
if time_speed and time_speed > 0 then
biome_lib.time_scale = 72 / time_speed
end
biome_lib.air = {name = "air"}
-- the mapgen rarely creates useful surfaces outside this range, but mods can
-- still specify a wider range if needed.
biome_lib.mapgen_elevation_limit = { ["min"] = -16, ["max"] = 48 }
-- Local functions
function biome_lib.dbg(msg, level)
local l = tonumber(level) or 0
if biome_lib.debug_log_level >= l then
print(os.date("%Y-%m-%d %H:%M:%S").." [Biome Lib]: "..msg)
minetest.log("verbose", "[Biome Lib]: "..msg)
end
end
local function get_biome_data(pos, perlin_fertile)
local fertility = perlin_fertile:get_2d({x=pos.x, y=pos.z})
local data = minetest.get_biome_data(pos)
-- Original values this method returned were +1 (lowest) to -1 (highest)
-- so we need to convert the 0-100 range from get_biome_data() to that.
return fertility, 1 - (data.heat / 100 * 2), 1 - (data.humidity / 100 * 2)
end
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 biome_lib.mapgen_elevation_limit.min
biome.max_elevation = biome.max_elevation or biome_lib.mapgen_elevation_limit.max
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.rarity_fertility = biome.rarity_fertility or 0
biome.max_count = biome.max_count or 125
biome.tries = biome.tries or 2
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
return biome
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_on_generate(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), 2)
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), 2)
end
biome_lib.mapgen_elevation_limit.min = math.min(biomedef.min_elevation or 0, biome_lib.mapgen_elevation_limit.min)
biome_lib.mapgen_elevation_limit.max = math.max(biomedef.max_elevation or 0, biome_lib.mapgen_elevation_limit.max)
local decor_def = biome_lib.can_use_decorations(biomedef, nodes_or_function_or_model)
if decor_def then
biome_lib.dbg("Using engine decorations instead of biome_lib functions for node(s): "..
dump(nodes_or_function_or_model), 3)
biome_lib.registered_decorations[#biome_lib.registered_decorations + 1] = nodes_or_function_or_model
minetest.register_decoration(decor_def)
return
elseif biomedef.check_air == false then
biome_lib.dbg("Register no-air-check mapgen hook: "..dump(nodes_or_function_or_model), 3)
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), 2)
end
else
for i = 1, #biomedef.surface do
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), 2)
end
end
end
else
biome_lib.dbg("Register with-air-checking mapgen hook: "..dump(nodes_or_function_or_model), 3)
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), 2)
end
else
for i = 1, #biomedef.surface do
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), 2)
end
end
end
end
end
-- Function to check whether a position matches the given biome definition
-- Returns true when the surface can be populated
local function populate_single_surface(biome, pos, perlin_fertile_area, checkair)
local p_top = { x = pos.x, y = pos.y + 1, z = pos.z }
if biome.rarity - biome.rarity_fertility == 100 then
return
end
local fertility, temperature, humidity = get_biome_data(pos, perlin_fertile_area)
if math.random() * 100 <= (biome.rarity - ((fertility + 1) / 2 * biome.rarity_fertility)) then
return
end
local pos_biome_ok = pos.y >= biome.min_elevation and pos.y <= biome.max_elevation
and fertility >= biome.plantlife_limit
and temperature <= biome.temp_min and temperature >= biome.temp_max
and humidity <= biome.humidity_min and humidity >= biome.humidity_max
if not pos_biome_ok then
return -- Y position mismatch, outside of biome
end
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 not surface_ok then
return -- Surface does not match the given node group/name
end
if checkair and minetest.get_node(p_top).name ~= "air" then
return
end
if biome.below_nodes and
not string.find(dump(biome.below_nodes),
minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name
) then
return -- Node below does not match
end
if biome.ncount and
#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 then
return -- Not enough similar biome nodes around
end
if biome.near_nodes and
#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 then
return -- Long distance neighbours do not match
end
-- Position fits into given biome
return true
end
function biome_lib.populate_surfaces(b, nodes_or_function_or_model, snodes, checkair)
local items_added = 0
local biome = biome_lib.set_defaults(b)
-- 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, biome_lib.fertile_perlin_octaves,
biome_lib.fertile_perlin_persistence, biome_lib.fertile_perlin_scale)
for i = 1, #snodes do
local pos = vector.new(snodes[i])
if populate_single_surface(biome, pos, perlin_fertile_area, checkair) 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
return 0
end
for i = 1, math.min(math.ceil(biome.max_count/25), num_in_biome_nodes) do
local tries = 0
local spawned = false
while tries < biome.tries and not spawned do
local pos = in_biome_nodes[math.random(1, num_in_biome_nodes)]
local will_place = true
local fdir = nil
if biome.random_facedir then
fdir = math.random(biome.random_facedir[1], biome.random_facedir[2])
end
if biome.spawn_on_side then
local onside = biome_lib.find_open_side(pos)
if onside then
pos = onside.newpos
fdir = onside.facedir
else
will_place = false
end
elseif biome.spawn_on_bottom then
if minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == "air" then
pos.y = pos.y - 1
else
will_place = false
end
elseif 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 will_place and 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.swap_node(p_top, biome_lib.air)
minetest.swap_node({x=p_top.x, y=p_top.y+1, z=p_top.z}, biome_lib.air)
end
if biome.delete_above_surround then
minetest.swap_node({x=p_top.x-1, y=p_top.y, z=p_top.z}, biome_lib.air)
minetest.swap_node({x=p_top.x+1, y=p_top.y, z=p_top.z}, biome_lib.air)
minetest.swap_node({x=p_top.x, y=p_top.y, z=p_top.z-1}, biome_lib.air)
minetest.swap_node({x=p_top.x, y=p_top.y, z=p_top.z+1}, biome_lib.air)
minetest.swap_node({x=p_top.x-1, y=p_top.y+1, z=p_top.z}, biome_lib.air)
minetest.swap_node({x=p_top.x+1, y=p_top.y+1, z=p_top.z}, biome_lib.air)
minetest.swap_node({x=p_top.x, y=p_top.y+1, z=p_top.z-1}, biome_lib.air)
minetest.swap_node({x=p_top.x, y=p_top.y+1, z=p_top.z+1}, biome_lib.air)
end
if biome.spawn_replace_node then
minetest.swap_node(pos, biome_lib.air)
end
local objtype = type(nodes_or_function_or_model)
if objtype == "table" then
if nodes_or_function_or_model.axiom then
biome_lib.generate_ltree(p_top, nodes_or_function_or_model)
biome_lib.dbg("An L-tree was spawned at "..minetest.pos_to_string(p_top), 4)
spawned = true
else
local n=nodes_or_function_or_model[math.random(#nodes_or_function_or_model)]
minetest.swap_node(p_top, { name = n, param2 = fdir })
biome_lib.dbg("Node \""..n.."\" was randomly picked from a list and placed at "..
minetest.pos_to_string(p_top), 4)
spawned = true
end
elseif objtype == "string" and
minetest.registered_nodes[nodes_or_function_or_model] then
minetest.swap_node(p_top, { name = nodes_or_function_or_model, param2 = fdir })
biome_lib.dbg("Node \""..nodes_or_function_or_model.."\" was placed at "..
minetest.pos_to_string(p_top), 4)
spawned = true
elseif objtype == "function" then
nodes_or_function_or_model(pos, fdir)
biome_lib.dbg("A function was run on surface node at "..minetest.pos_to_string(pos), 4)
spawned = true
elseif objtype == "string" and pcall(loadstring(("return %s(...)"):
format(nodes_or_function_or_model)),pos) then
spawned = true
biome_lib.dbg("An obsolete string-specified function was run on surface node at "..
minetest.pos_to_string(p_top), 4)
else
biome_lib.dbg("Warning: Ignored invalid definition for object "..
dump(nodes_or_function_or_model).." that was pointed at {"..dump(pos).."}", 2)
end
else
tries = tries + 1
end
end
if spawned then items_added = items_added + 1 end
end
return items_added
end
-- Primary log read-out/mapgen spawner
local function confirm_block_surroundings(p)
local n=minetest.get_node_or_nil(p)
if not n or n.name == "ignore" then return false end
for x = -32,32,64 do -- step of 64 causes it to only check the 8 corner blocks
for y = -32,32,64 do
for z = -32,32,64 do
n = minetest.get_node_or_nil({x=p.x + x, y=p.y + y, z=p.z + z})
if not n or n.name == "ignore" then return false end
end
end
end
return true
end
function biome_lib.generate_block(shutting_down)
if shutting_down then
if #biome_lib.block_recheck_list > 0 then
for i = 1, #biome_lib.block_recheck_list do
biome_lib.block_log[#biome_lib.block_log + 1] = biome_lib.block_recheck_list[i]
end
biome_lib.block_recheck_list = {}
end
biome_lib.run_block_recheck_list = false
else
if biome_lib.run_block_recheck_list
and not biome_lib.block_recheck_list[1] then
biome_lib.run_block_recheck_list = false
end
end
local blocklog = biome_lib.run_block_recheck_list
and biome_lib.block_recheck_list
or biome_lib.block_log
if not blocklog[1] then return end
local minp = blocklog[1][1]
local maxp = blocklog[1][2]
local airflag = blocklog[1][3]
if not biome_lib.pos_hash then -- we need to read the maplock and get the surfaces list
local now = minetest.get_us_time()
biome_lib.pos_hash = {}
minetest.load_area(minp)
if not confirm_block_surroundings(minp)
and not shutting_down
-- if any neighbors appear not to be loaded and the block hasn't expired yet, defer it
and (blocklog[1][4] + biome_lib.block_timeout) > now then
if biome_lib.run_block_recheck_list then
biome_lib.block_log[#biome_lib.block_log + 1] = table.copy(biome_lib.block_recheck_list[1])
table.remove(biome_lib.block_recheck_list, 1)
else
biome_lib.block_recheck_list[#biome_lib.block_recheck_list + 1] = table.copy(biome_lib.block_log[1])
table.remove(biome_lib.block_log, 1)
end
biome_lib.pos_hash = nil
biome_lib.dbg("Mapblock at "..minetest.pos_to_string(minp)..
" had a neighbor not fully emerged, skipped it for now.", 4)
return
else
biome_lib.pos_hash.surface_node_list = airflag
and minetest.find_nodes_in_area_under_air(minp, maxp, biome_lib.surfaceslist_aircheck)
or minetest.find_nodes_in_area(minp, maxp, biome_lib.surfaceslist_no_aircheck)
if #biome_lib.pos_hash.surface_node_list == 0 then
biome_lib.dbg("Mapblock at "..minetest.pos_to_string(minp).." dequeued: no detected surfaces.", 4)
table.remove(blocklog, 1)
biome_lib.pos_hash = nil
return
else
biome_lib.pos_hash.action_index = 1
biome_lib.dbg("Mapblock at "..minetest.pos_to_string(minp)..
" has "..#biome_lib.pos_hash.surface_node_list..
" surface nodes to work on (airflag="..dump(airflag)..")", 4)
end
end
elseif not (airflag and biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index])
and not (not airflag and biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index]) then
-- the block is finished, remove it
if #biome_lib.pos_hash.surface_node_list > 0 then
biome_lib.dbg("Deleted mapblock "..minetest.pos_to_string(minp).." from the block log", 4)
end
table.remove(blocklog, 1)
biome_lib.pos_hash = nil
else
-- below, [1] is biome, [2] is the thing to be added
local added = 0
if airflag then
if biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index] then
added = biome_lib.populate_surfaces(
biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index][1],
biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index][2],
biome_lib.pos_hash.surface_node_list, true)
biome_lib.pos_hash.action_index = biome_lib.pos_hash.action_index + 1
end
else
if biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index] then
added = biome_lib.populate_surfaces(
biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index][1],
biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index][2],
biome_lib.pos_hash.surface_node_list, false)
biome_lib.pos_hash.action_index = biome_lib.pos_hash.action_index + 1
end
end
if added > 0 then
biome_lib.dbg("biome_lib.populate_surfaces ran on mapblock at "..
minetest.pos_to_string(minp)..". Entry #"..
(biome_lib.pos_hash.action_index-1).." added "..added.." items.", 4)
end
end
end
-- The spawning ABM
function biome_lib.register_active_spawner(sd,sp,sr,sc,ss,sa)
local b = {}
if type(sd) ~= "table" then
b.spawn_delay = sd -- old api expects ABM interval param here.
b.spawn_plants = {sp}
b.avoid_radius = sr
b.spawn_chance = sc
b.spawn_surfaces = {ss}
b.avoid_nodes = sa
else
b = sd
end
if b.spawn_delay*biome_lib.time_scale >= 1 then
b.interval = b.spawn_delay*biome_lib.time_scale
else
b.interval = 1
end
local biome = biome_lib.set_defaults(b)
biome.spawn_plants_count = #(biome.spawn_plants)
local n
if type(biome.spawn_plants) == "table" then
n = "random: "..biome.spawn_plants[1]..", ..."
else
n = biome.spawn_plants
end
biome.label = biome.label or "biome_lib spawn_on_surfaces(): "..n
minetest.register_abm({
nodenames = biome.spawn_surfaces,
interval = biome.interval,
chance = biome.spawn_chance,
neighbors = biome.neighbors,
label = biome.label,
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, biome_lib.fertile_perlin_octaves,
biome_lib.fertile_perlin_persistence, biome_lib.fertile_perlin_scale)
local fertility, temperature, humidity = get_biome_data(pos, perlin_fertile_area)
local pos_biome_ok = pos.y >= biome.min_elevation and pos.y <= biome.max_elevation
and fertility > biome.plantlife_limit
and temperature <= biome.temp_min and temperature >= biome.temp_max
and humidity <= biome.humidity_min and humidity >= biome.humidity_max
and biome_lib.is_node_loaded(p_top)
if not pos_biome_ok then
return -- Outside of biome
end
local n_light = minetest.get_node_light(p_top, nil)
if n_light < biome.light_min or n_light > biome.light_max then
return -- Too dark or too bright
end
if 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
return -- Nodes to avoid are nearby
end
if biome.neighbors and biome.ncount and
#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 then
return -- Near neighbour nodes are not present
end
local NEAR_DST = biome.near_nodes_size
if biome.near_nodes and biome.near_nodes_count and biome.near_nodes_size and
#minetest.find_nodes_in_area(
{x=pos.x-NEAR_DST, y=pos.y-biome.near_nodes_vertical, z=pos.z-NEAR_DST},
{x=pos.x+NEAR_DST, y=pos.y+biome.near_nodes_vertical, z=pos.z+NEAR_DST},
biome.near_nodes
) < biome.near_nodes_count then
return -- Far neighbour nodes are not present
end
if (biome.air_count and biome.air_size) and
#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 then
return -- Not enough air
end
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.swap_node(p_top, { name = biome.alt_wallnode, param2 = walldir })
end
return
end
local currentsurface = minetest.get_node(pos).name
if biome_lib.default_water_nodes[currentsurface] and
#minetest.find_nodes_in_area(
{x=pos.x, y=pos.y-biome.depth_max-1, z=pos.z},
vector.new(pos),
biome_lib.default_wet_surfaces
) == 0 then
return -- On water but no ground nearby
end
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.swap_node(p_top, { name = plant_to_spawn, param2 = fdir })
end
elseif biome.spawn_replace_node then
minetest.swap_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.swap_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.swap_node({x=pos.x, y=pos.y-1, z=pos.z}, { name = plant_to_spawn, param2 = fdir} )
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_plant(pos, replacement, grow_function, walldir, seeddiff)
local growtype = type(grow_function)
if growtype == "table" then
minetest.swap_node(pos, biome_lib.air)
biome_lib.grow_ltree(pos, grow_function)
return
elseif growtype == "function" then
local perlin_fertile_area = minetest.get_perlin(seeddiff, biome_lib.fertile_perlin_octaves,
biome_lib.fertile_perlin_persistence, biome_lib.fertile_perlin_scale)
local fertility, temperature, _ = get_biome_data(pos, perlin_fertile_area)
grow_function(pos, fertility, temperature, walldir)
return
elseif growtype == "string" then
local perlin_fertile_area = minetest.get_perlin(seeddiff, biome_lib.fertile_perlin_octaves,
biome_lib.fertile_perlin_persistence, biome_lib.fertile_perlin_scale)
local fertility, temperature, _ = get_biome_data(pos, perlin_fertile_area)
assert(loadstring(grow_function.."(...)"))(pos, fertility, temperature, walldir)
return
elseif growtype == "nil" then
minetest.swap_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
-- Check for infinite stacks
if minetest.get_modpath("unified_inventory") or not minetest.settings:get_bool("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

View File

@ -1,141 +0,0 @@
-- Iterate through the mapblock log,
-- populating blocks with new stuff in the process.
minetest.register_globalstep(function(dtime)
if not biome_lib.block_log[1] then return end -- the block log is empty
if math.random(100) > biome_lib.queue_ratio then return end
for s = 1, biome_lib.entries_per_step do
biome_lib.generate_block()
end
end)
-- Periodically wake-up the queue to give old blocks a chance to time-out
-- if the player isn't currently exploring (i.e. they're just playing in one area)
function biome_lib.wake_up_queue()
if #biome_lib.block_recheck_list > 1
and #biome_lib.block_log == 0 then
biome_lib.block_log[#biome_lib.block_log + 1] =
table.copy(biome_lib.block_recheck_list[#biome_lib.block_recheck_list])
biome_lib.block_recheck_list[#biome_lib.block_recheck_list] = nil
biome_lib.run_block_recheck_list = true
biome_lib.dbg("Woke-up the map queue to give old blocks a chance to time-out.", 3)
end
minetest.after(biome_lib.block_queue_wakeup_time, biome_lib.wake_up_queue)
end
biome_lib.wake_up_queue()
-- Play out the entire log all at once on shutdown
-- to prevent unpopulated map areas
local function format_time(t)
if t > 59999999 then
return os.date("!%M minutes and %S seconds", math.ceil(t/1000000))
else
return os.date("!%S seconds", math.ceil(t/1000000))
end
end
function biome_lib.check_remaining_time()
if minetest.get_us_time() > (biome_lib.shutdown_last_timestamp + 10000000) then -- report progress every 10s
biome_lib.shutdown_last_timestamp = minetest.get_us_time()
local entries_remaining = #biome_lib.block_log + #biome_lib.block_recheck_list
local total_purged = biome_lib.starting_count - entries_remaining
local elapsed_time = biome_lib.shutdown_last_timestamp - biome_lib.shutdown_start_time
biome_lib.dbg(string.format("%i entries, approximately %s remaining.",
entries_remaining, format_time(elapsed_time/total_purged * entries_remaining)))
end
end
--Purge the block log at shutdown
minetest.register_on_shutdown(function()
biome_lib.shutdown_start_time = minetest.get_us_time()
biome_lib.shutdown_last_timestamp = minetest.get_us_time()+1
biome_lib.starting_count = #biome_lib.block_log + #biome_lib.block_recheck_list
if biome_lib.starting_count == 0 then
return
end
biome_lib.dbg("Stand by, purging the mapblock log "..
"(there are "..biome_lib.starting_count.." entries) ...", 0)
while #biome_lib.block_log > 0 do
biome_lib.generate_block(true)
biome_lib.check_remaining_time()
end
if #biome_lib.block_recheck_list > 0 then
biome_lib.block_log = table.copy(biome_lib.block_recheck_list)
biome_lib.block_recheck_list = {}
while #biome_lib.block_log > 0 do
biome_lib.generate_block(true)
biome_lib.check_remaining_time()
end
end
biome_lib.dbg("Log purge completed after "..
format_time(minetest.get_us_time() - biome_lib.shutdown_start_time)..".", 0)
end)
-- "Record" the map chunks being generated by the core mapgen,
-- split into individual mapblocks to reduce lag
minetest.register_on_generated(function(minp, maxp, blockseed)
local timestamp = minetest.get_us_time()
for y = 0, 4 do
local miny = minp.y + y*16
if miny >= biome_lib.mapgen_elevation_limit.min
and (miny + 15) <= biome_lib.mapgen_elevation_limit.max then
for x = 0, 4 do
local minx = minp.x + x*16
for z = 0, 4 do
local minz = minp.z + z*16
local bmin = {x=minx, y=miny, z=minz}
local bmax = {x=minx + 15, y=miny + 15, z=minz + 15}
biome_lib.block_log[#biome_lib.block_log + 1] = { bmin, bmax, true, timestamp }
biome_lib.block_log[#biome_lib.block_log + 1] = { bmin, bmax, false, timestamp }
end
end
else
biome_lib.dbg("Did not enqueue mapblocks at elevation "..miny..
"m, they're out of range of any generate_plant() calls.", 4)
end
end
biome_lib.run_block_recheck_list = true
end)
if biome_lib.debug_log_level >= 3 then
biome_lib.last_count = 0
function biome_lib.show_pending_block_count()
if biome_lib.last_count ~= #biome_lib.block_log then
biome_lib.dbg(string.format("Pending block counts - ready to process: %-8icurrently deferred: %i",
#biome_lib.block_log, #biome_lib.block_recheck_list), 3)
biome_lib.last_count = #biome_lib.block_log
biome_lib.queue_idle_flag = false
elseif not biome_lib.queue_idle_flag then
if #biome_lib.block_recheck_list > 0 then
biome_lib.dbg("Mapblock queue only contains blocks that can't yet be processed.", 3)
biome_lib.dbg("Idling the queue until new blocks arrive or the next wake-up call occurs.", 3)
else
biome_lib.dbg("Mapblock queue has run dry.", 3)
biome_lib.dbg("Idling the queue until new blocks arrive.", 3)
end
biome_lib.queue_idle_flag = true
end
minetest.after(1, biome_lib.show_pending_block_count)
end
biome_lib.show_pending_block_count()
end

View File

@ -1,115 +0,0 @@
-- compatibility shims for old mods
function biome_lib:register_generate_plant(b, n)
biome_lib.dbg("Warning: biome_lib:register_generate_plant() is deprecated!", 2)
biome_lib.dbg("Use biome_lib.register_on_generate() instead", 2)
biome_lib.dbg("Item: "..dump(n), 2)
biome_lib.register_on_generate(b, n)
end
function biome_lib:spawn_on_surfaces(sd, sp, sr, sc, ss, sa)
biome_lib.dbg("Warning: biome_lib:spawn_on_surfaces() is deprecated!", 2)
biome_lib.dbg("Use biome_lib.register_active_spawner() instead.", 2)
biome_lib.dbg("Item: "..dump(sd.spawn_plants or sp[1] or sp), 2)
biome_lib.register_active_spawner(sd, sp, sr, sc, ss, sa)
end
function biome_lib:replace_object(p, r, f, w, d)
biome_lib.dbg("Warning: biome_lib:replace_object() is deprecated!", 2)
biome_lib.dbg("Use biome_lib.replace_plant() instead.", 2)
biome_lib.dbg("Item: "..dump(r), 2)
biome_lib.replace_plant(p, r, f, w, d)
end
function biome_lib:grow_plants(o)
biome_lib.dbg("Warning: biome_lib:grow_plants() is deprecated!", 2)
biome_lib.dbg("Use biome_lib.update_plant() instead.", 2)
biome_lib.dbg("Item: "..dump(o.grow_nodes), 2)
biome_lib.update_plant(o)
end
function biome_lib.generate_ltree(p, n)
minetest.spawn_tree(p, n)
end
function biome_lib.grow_ltree(p, n)
minetest.spawn_tree(p, n)
end
function biome_lib:generate_tree(p, n)
biome_lib.dbg("Warning: biome_lib:generate_tree() is deprecated!", 2)
biome_lib.dbg("Use biome_lib.generate_ltree() instead.", 2)
biome_lib.dbg("Item: "..dump(n), 2)
biome_lib.generate_ltree(p, n)
end
function biome_lib:grow_tree(p, n)
biome_lib.dbg("Warning: biome_lib:grow_tree() is deprecated!", 2)
biome_lib.dbg("Use biome_lib.grow_ltree() instead.", 2)
biome_lib.dbg("Item: "..dump(n), 2)
biome_lib.grow_ltree(p, n)
end
function biome_lib.can_use_decorations(b, nodes_or_function_or_treedef)
if not b or not nodes_or_function_or_treedef
or b.below_nodes
or b.avoid_nodes
or b.avoid_radius
or b.neighbors
or b.ncount
or b.depth
or b.near_nodes_size
or b.near_nodes_vertical
or b.temp_min
or b.temp_max
or b.verticals_list
or b.delete_above
or b.delete_above_surround
or ( type(nodes_or_function_or_treedef) == "string" and not minetest.registered_nodes[nodes_or_function_or_treedef] )
or ( type(nodes_or_function_or_treedef) == "table" and nodes_or_function_or_treedef.axiom )
or type(nodes_or_function_or_treedef) == "function"
then return false
end
local biome = biome_lib.set_defaults(b)
local decor_def = {
["deco_type"] = "simple",
["flags"] = "all_floors",
["decoration"] = nodes_or_function_or_treedef,
["place_on"] = biome.surface,
["y_min"] = biome.min_elevation,
["y_max"] = biome.max_elevation,
["spawn_by"] = biome.near_nodes,
["num_spawn_by"] = biome.near_nodes and biome.near_nodes_count,
}
local r = (100-biome.rarity)/100
local mc = math.min(biome.max_count, 6400)/6400
decor_def.noise_params = {
octaves = biome_lib.fertile_perlin_octaves,
persist = biome_lib.fertile_perlin_persistence * (100/biome_lib.fertile_perlin_scale),
scale = math.min(r, mc),
seed = biome.seed_diff,
offset = 0,
spread = {x = 100, y = 100, z = 100},
lacunarity = 2,
flags = "absvalue"
}
if not b.check_air then
decor_def.flags = decor_def.flags..",force_placement"
end
if b.spawn_replace_node then
decor_def.place_offset_y = -1
end
if b.random_facedir then
decor_def.param2 = math.min(b.random_facedir[1], b.random_facedir[2])
decor_def.param2_max = math.max(b.random_facedir[1], b.random_facedir[2])
end
return decor_def
end

3
depends.txt Normal file
View File

@ -0,0 +1,3 @@
default
intllib?

1
description.txt Normal file
View File

@ -0,0 +1 @@
The biome spawning and management library for Plantlife, Moretrees, Tiny Trees, and other mods that originally depended on plants_lib from the plantlife modpack.

View File

@ -1,3 +1,5 @@
local time_scale = ...
-- The growing ABM -- The growing ABM
function biome_lib.check_surface(name, nodes) function biome_lib.check_surface(name, nodes)
@ -13,13 +15,13 @@ function biome_lib.check_surface(name, nodes)
return false return false
end end
function biome_lib.update_plant(opts) function biome_lib:grow_plants(opts)
local options = opts local options = opts
options.height_limit = options.height_limit or 5 options.height_limit = options.height_limit or 5
options.ground_nodes = options.ground_nodes or biome_lib.default_ground_nodes options.ground_nodes = options.ground_nodes or { "default:dirt_with_grass" }
options.grow_nodes = options.grow_nodes or biome_lib.default_grow_nodes options.grow_nodes = options.grow_nodes or { "default:dirt_with_grass" }
options.seed_diff = options.seed_diff or 0 options.seed_diff = options.seed_diff or 0
local n local n
@ -30,10 +32,10 @@ function biome_lib.update_plant(opts)
n = options.grow_plant n = options.grow_plant
end end
options.label = options.label or "biome_lib.update_plant(): "..n options.label = options.label or "biome_lib grow_plants(): "..n
if options.grow_delay*biome_lib.time_scale >= 1 then if options.grow_delay*time_scale >= 1 then
options.interval = options.grow_delay*biome_lib.time_scale options.interval = options.grow_delay*time_scale
else else
options.interval = 1 options.interval = 1
end end
@ -48,14 +50,15 @@ function biome_lib.update_plant(opts)
local p_bot = {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_top = minetest.get_node(p_top)
local n_bot = minetest.get_node(p_bot) 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 local walldir = nil
if options.need_wall and options.verticals_list then if options.need_wall and options.verticals_list then
walldir = biome_lib.find_adjacent_wall(p_top, options.verticals_list, options.choose_random_wall) walldir = biome_lib:find_adjacent_wall(p_top, options.verticals_list, options.choose_random_wall)
end end
if biome_lib.default_grow_through_nodes[n_top.name] if (n_top.name == "air" or n_top.name == "default:snow")
and (not options.need_wall or (options.need_wall and walldir)) then and (not options.need_wall or (options.need_wall and walldir)) then
if options.grow_vertically and walldir then if options.grow_vertically and walldir then
if biome_lib.search_downward(pos, options.height_limit, options.ground_nodes) then if biome_lib:search_downward(pos, options.height_limit, options.ground_nodes) then
minetest.swap_node(p_top, { name = options.grow_plant, param2 = walldir}) minetest.swap_node(p_top, { name = options.grow_plant, param2 = walldir})
end end
@ -64,10 +67,24 @@ function biome_lib.update_plant(opts)
minetest.swap_node(pos, biome_lib.air) minetest.swap_node(pos, biome_lib.air)
else else
biome_lib.replace_plant(pos, options.grow_result, options.grow_function, options.facedir, options.seed_diff) biome_lib:replace_object(pos, options.grow_result, options.grow_function, options.facedir, options.seed_diff)
end end
end end
end end
end end
}) })
end 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

799
init.lua
View File

@ -1,95 +1,736 @@
-- Biome library mod by VanessaE -- Biome library mod by Vanessa Ezekowitz
-- --
-- I got the temperature map idea from "hmmmm", values used for it came from -- I got the temperature map idea from "hmmmm", values used for it came from
-- Splizard's snow mod. -- Splizard's snow mod.
-- --
-- Various settings - most of these probably won't need to be changed
biome_lib = {} biome_lib = {}
biome_lib.air = {name = "air"}
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.modpath = minetest.get_modpath("biome_lib")
local function tableize(s) biome_lib.total_no_aircheck_calls = 0
return string.split(string.trim(string.gsub(s, " ", "")))
end
local c1 = minetest.settings:get("biome_lib_default_grow_through_nodes") biome_lib.queue_run_ratio = tonumber(minetest.settings:get("biome_lib_queue_run_ratio")) or 100
biome_lib.default_grow_through_nodes = {["air"] = true}
if c1 then
for _, i in ipairs(tableize(c1)) do
biome_lib.default_grow_through_nodes[i] = true
end
else
biome_lib.default_grow_through_nodes["default:snow"] = true
end
local c2 = minetest.settings:get("biome_lib_default_water_nodes") -- Boilerplate to support localized strings if intllib mod is installed.
biome_lib.default_water_nodes = {} local S
if c2 then if minetest.global_exists("intllib") then
for _, i in ipairs(tableize(c2)) do if intllib.make_gettext_pair then
biome_lib.default_water_nodes[i] = true S = intllib.make_gettext_pair()
end
else
biome_lib.default_water_nodes["default:water_source"] = true
biome_lib.default_water_nodes["default:water_flowing"] = true
biome_lib.default_water_nodes["default:river_water_source"] = true
biome_lib.default_water_nodes["default:river_water_flowing"] = true
end
local c3 = minetest.settings:get("biome_lib_default_wet_surfaces")
local c4 = minetest.settings:get("biome_lib_default_ground_nodes")
local c5 = minetest.settings:get("biome_lib_default_grow_nodes")
biome_lib.default_wet_surfaces = c3 and tableize(c3) or {"default:dirt", "default:dirt_with_grass", "default:sand"}
biome_lib.default_ground_nodes = c4 and tableize(c4) or {"default:dirt_with_grass"}
biome_lib.default_grow_nodes = c5 and tableize(c5) or {"default:dirt_with_grass"}
biome_lib.debug_log_level = tonumber(minetest.settings:get("biome_lib_debug_log_level")) or 0
local rr = tonumber(minetest.settings:get("biome_lib_queue_ratio")) or -200
biome_lib.queue_ratio = 100 - rr
biome_lib.entries_per_step = math.max(-rr, 1)
-- the timer that manages the block timeout is in microseconds, but the timer
-- that manages the queue wakeup call has to be in seconds, and works best if
-- it takes a fraction of the block timeout interval.
local t = tonumber(minetest.settings:get("biome_lib_block_timeout")) or 300
biome_lib.block_timeout = t * 1000000
-- we don't want the wakeup function to trigger TOO often,
-- in case the user's block timeout setting is really low
biome_lib.block_queue_wakeup_time = math.min(t/2, math.max(20, t/10))
-- the actual important stuff starts here ;-)
dofile(biome_lib.modpath .. "/api.lua")
dofile(biome_lib.modpath .. "/search_functions.lua")
dofile(biome_lib.modpath .. "/growth.lua")
dofile(biome_lib.modpath .. "/compat.lua")
minetest.after(0.01, function()
-- report the final registration results and enable the active block queue stuff
local n = #biome_lib.actionslist_aircheck + #biome_lib.actionslist_no_aircheck
biome_lib.dbg("All mapgen registrations completed.", 0)
if n > 0 then
biome_lib.dbg("Total items/actions to handle manually: "..n..
" ("..#biome_lib.actionslist_no_aircheck.." without air checks)", 0)
biome_lib.dbg("Total surface types to handle manually: "
..#biome_lib.surfaceslist_aircheck + #biome_lib.surfaceslist_no_aircheck, 0)
else else
biome_lib.dbg("There are no \"handle manually\" items/actions registered,", 0) S = intllib.Getter()
biome_lib.dbg("so the mapblock queue will not be used this session.", 0) end
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.settings: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
local function get_biome_data(pos, perlin_fertile)
local fertility = perlin_fertile:get_2d({x=pos.x, y=pos.z})
if type(minetest.get_biome_data) == "function" then
local data = minetest.get_biome_data(pos)
if data then
return fertility, data.heat / 100, data.humidity / 100
end
end end
biome_lib.dbg("Items sent to the engine's decorations handler: "..#biome_lib.registered_decorations, 0) local temperature = biome_lib.perlin_temperature:get2d({x=pos.x, y=pos.z})
biome_lib.dbg("Elevation range: "..biome_lib.mapgen_elevation_limit.min.." to ".. local humidity = biome_lib.perlin_humidity:get2d({x=pos.x+150, y=pos.z+50})
string.format("%+d", biome_lib.mapgen_elevation_limit.max).." meters.", 0)
if n > 0 then return fertility, temperature, humidity
dofile(biome_lib.modpath .. "/block_queue_checks.lua") end
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 to check whether a position matches the given biome definition
-- Returns true when the surface can be populated
local function populate_single_surface(biome, pos, perlin_fertile_area, checkair)
local p_top = { x = pos.x, y = pos.y + 1, z = pos.z }
if math.random(1, 100) <= biome.rarity then
return
end
local fertility, temperature, humidity = get_biome_data(pos, perlin_fertile_area)
local pos_biome_ok = pos.y >= biome.min_elevation and pos.y <= biome.max_elevation
and fertility > biome.plantlife_limit
and temperature <= biome.temp_min and temperature >= biome.temp_max
and humidity <= biome.humidity_min and humidity >= biome.humidity_max
if not pos_biome_ok then
return -- Y position mismatch, outside of biome
end
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 not surface_ok then
return -- Surface does not match the given node group/name
end
if checkair and minetest.get_node(p_top).name ~= "air" then
return
end
if biome.below_nodes and
not string.find(dump(biome.below_nodes),
minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name
) then
return -- Node below does not match
end
if biome.ncount and
#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 then
return -- Not enough similar biome nodes around
end
if biome.near_nodes and
#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 then
return -- Long distance neighbours do not match
end
-- Position fits into given biome
return true
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 = vector.new(snodes[i])
if populate_single_surface(biome, pos, perlin_fertile_area, checkair) 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
return
end
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.swap_node(p_top, biome_lib.air)
minetest.swap_node({x=p_top.x, y=p_top.y+1, z=p_top.z}, biome_lib.air)
end
if biome.delete_above_surround then
minetest.swap_node({x=p_top.x-1, y=p_top.y, z=p_top.z}, biome_lib.air)
minetest.swap_node({x=p_top.x+1, y=p_top.y, z=p_top.z}, biome_lib.air)
minetest.swap_node({x=p_top.x, y=p_top.y, z=p_top.z-1}, biome_lib.air)
minetest.swap_node({x=p_top.x, y=p_top.y, z=p_top.z+1}, biome_lib.air)
minetest.swap_node({x=p_top.x-1, y=p_top.y+1, z=p_top.z}, biome_lib.air)
minetest.swap_node({x=p_top.x+1, y=p_top.y+1, z=p_top.z}, biome_lib.air)
minetest.swap_node({x=p_top.x, y=p_top.y+1, z=p_top.z-1}, biome_lib.air)
minetest.swap_node({x=p_top.x, y=p_top.y+1, z=p_top.z+1}, biome_lib.air)
end
if biome.spawn_replace_node then
minetest.swap_node(pos, biome_lib.air)
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(p_top, 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.swap_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.swap_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
-- 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
return
end
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
-- 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
return
end
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
-- "Play" them back, populating them with new stuff in the process
local step_duration = tonumber(minetest.settings:get("dedicated_server_step"))
minetest.register_globalstep(function(dtime)
if dtime >= step_duration + 0.1 -- don't attempt to populate if lag is already too high
or math.random(100) > biome_lib.queue_run_ratio
or (#biome_lib.blocklist_aircheck == 0 and #biome_lib.blocklist_no_aircheck == 0) then
return
end
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) end)
-- Play out the entire log all at once on shutdown
-- to prevent unpopulated map areas
minetest.register_on_shutdown(function()
if #biome_lib.blocklist_aircheck == 0 then
return
end
print("[biome_lib] Stand by, playing out the rest of the aircheck mapblock log")
print("(there are "..#biome_lib.blocklist_aircheck.." entries)...")
while #biome_lib.blocklist_aircheck > 0 do
biome_lib:generate_block_with_air_checking(0.1)
end
end)
minetest.register_on_shutdown(function()
if #biome_lib.blocklist_aircheck == 0 then
return
end
print("[biome_lib] Stand by, playing out the rest of the no-aircheck mapblock log")
print("(there are "..#biome_lib.blocklist_no_aircheck.." entries)...")
while #biome_lib.blocklist_no_aircheck > 0 do
biome_lib:generate_block_no_aircheck(0.1)
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)
local n
if type(biome.spawn_plants) == "table" then
n = "random: "..biome.spawn_plants[1]..", ..."
else
n = biome.spawn_plants
end
biome.label = biome.label or "biome_lib spawn_on_surfaces(): "..n
minetest.register_abm({
nodenames = biome.spawn_surfaces,
interval = biome.interval,
chance = biome.spawn_chance,
neighbors = biome.neighbors,
label = biome.label,
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 fertility, temperature, humidity = get_biome_data(pos, perlin_fertile_area)
local pos_biome_ok = pos.y >= biome.min_elevation and pos.y <= biome.max_elevation
and fertility > biome.plantlife_limit
and temperature <= biome.temp_min and temperature >= biome.temp_max
and humidity <= biome.humidity_min and humidity >= biome.humidity_max
and biome_lib:is_node_loaded(p_top)
if not pos_biome_ok then
return -- Outside of biome
end
local n_light = minetest.get_node_light(p_top, nil)
if n_light < biome.light_min or n_light > biome.light_max then
return -- Too dark or too bright
end
if 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
return -- Nodes to avoid are nearby
end
if biome.neighbors and biome.ncount and
#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 then
return -- Near neighbour nodes are not present
end
local NEAR_DST = biome.near_nodes_size
if biome.near_nodes and biome.near_nodes_count and biome.near_nodes_size and
#minetest.find_nodes_in_area(
{x=pos.x-NEAR_DST, y=pos.y-biome.near_nodes_vertical, z=pos.z-NEAR_DST},
{x=pos.x+NEAR_DST, y=pos.y+biome.near_nodes_vertical, z=pos.z+NEAR_DST},
biome.near_nodes
) < biome.near_nodes_count then
return -- Far neighbour nodes are not present
end
if (biome.air_count and biome.air_size) and
#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 then
return -- Not enough air
end
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.swap_node(p_top, { name = biome.alt_wallnode, param2 = walldir })
end
return
end
local currentsurface = minetest.get_node(pos).name
if currentsurface == "default:water_source" and
#minetest.find_nodes_in_area(
{x=pos.x, y=pos.y-biome.depth_max-1, z=pos.z},
vector.new(pos),
{"default:dirt", "default:dirt_with_grass", "default:sand"}
) == 0 then
return -- On water but no ground nearby
end
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.swap_node(p_top, { name = plant_to_spawn, param2 = fdir })
end
elseif biome.spawn_replace_node then
minetest.swap_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.swap_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.swap_node({x=pos.x, y=pos.y-1, z=pos.z}, { name = plant_to_spawn, param2 = fdir} )
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.swap_node(pos, biome_lib.air)
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 fertility, temperature, _ = get_biome_data(pos, perlin_fertile_area)
grow_function(pos, fertility, temperature, walldir)
return
elseif growtype == "string" then
local perlin_fertile_area = minetest.get_perlin(seeddiff, perlin_octaves, perlin_persistence, perlin_scale)
local fertility, temperature, _ = get_biome_data(pos, perlin_fertile_area)
assert(loadstring(grow_function.."(...)"))(pos, fertility, temperature, walldir)
return
elseif growtype == "nil" then
minetest.swap_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
dofile(biome_lib.modpath .. "/search_functions.lua")
assert(loadfile(biome_lib.modpath .. "/growth.lua"))(time_scale)
-- Check for infinite stacks
if minetest.get_modpath("unified_inventory") or not minetest.settings:get_bool("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)

5
locale/de.txt Normal file
View File

@ -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

5
locale/fr.txt Normal file
View File

@ -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.

5
locale/ru.txt Normal file
View File

@ -0,0 +1,5 @@
# Translation by inpos
someone = кто-то
Sorry, %s owns that spot. = Извините, но %s уже является владельцем этой точки.
[Plantlife Library] Loaded = [Plantlife Library] Загружена

5
locale/template.txt Normal file
View File

@ -0,0 +1,5 @@
# Template
someone =
Sorry, %s owns that spot. =
[Plantlife Library] Loaded =

5
locale/tr.txt Normal file
View File

@ -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

View File

@ -1,5 +1,2 @@
name = biome_lib name = biome_lib
title = Biome Library
description = The biome spawning and management library for Plantlife, Moretrees, Tiny Trees, and other mods that originally depended on plants_lib from the plantlife modpack.
optional_depends = default
min_minetest_version = 5.2.0 min_minetest_version = 5.2.0

View File

@ -2,11 +2,11 @@
-- function to decide if a node has a wall that's in verticals_list{} -- function to decide if a node has a wall that's in verticals_list{}
-- returns wall direction of valid node, or nil if invalid. -- returns wall direction of valid node, or nil if invalid.
function biome_lib.find_adjacent_wall(pos, verticals, randomflag) function biome_lib:find_adjacent_wall(pos, verticals, randomflag)
local verts = dump(verticals) local verts = dump(verticals)
if randomflag then if randomflag then
local walltab = {} 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] = 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+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] = 5 end
@ -27,7 +27,7 @@ end
-- node that matches the ground table. Returns the new position, or nil if -- node that matches the ground table. Returns the new position, or nil if
-- height limit is exceeded before finding it. -- height limit is exceeded before finding it.
function biome_lib.search_downward(pos, heightlimit, ground) function biome_lib:search_downward(pos, heightlimit, ground)
for i = 0, heightlimit do 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 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} return {x=pos.x, y=pos.y-i, z = pos.z}
@ -36,7 +36,7 @@ function biome_lib.search_downward(pos, heightlimit, ground)
return false return false
end end
function biome_lib.find_open_side(pos) function biome_lib:find_open_side(pos)
if minetest.get_node({ x=pos.x-1, y=pos.y, z=pos.z }).name == "air" then 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} return {newpos = { x=pos.x-1, y=pos.y, z=pos.z }, facedir = 2}
end end
@ -51,3 +51,10 @@ function biome_lib.find_open_side(pos)
end end
return nil return nil
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 }
biome_lib.blocklist_no_aircheck[#biome_lib.blocklist_no_aircheck + 1] = { minp, maxp }
end)

View File

@ -1,48 +0,0 @@
# Comma-separated list of things that a spawned node is allowed to grow
# through. Air is always added to whatever else you specify here.
biome_lib_default_grow_through_nodes (List of things a plant can grow through) string default:snow
# Comma-separated list of nodes that should be treated as water or water-like
# for the sake of looking for neighboring wet ground.
biome_lib_default_water_nodes (List of "water-like" sources) string default:water_source,default:water_flowing,default:river_water_source,default:river_water_flowing
# Comma-separated list of nodes that should be considered "wet" if one of
# the configured "water-like" sources is nearby.
biome_lib_default_wet_surfaces (List of "wet" nodes) string default:dirt,default:dirt_with_grass,default:sand
# Comma-separated list of nodes that something must be sitting on to be
# able to actively change from one thing to another (such as a sapling
# growing into a tree), to be used if the mod that added that growable
# thing didn't provide its own list of suitable surfaces.
biome_lib_default_grow_nodes (List of default surfaces a plant can thrive on) string default:dirt_with_grass
# Comma-separated list of nodes to use as the "root" of something that can
# gradually climb up a wall (such as ivy), to be used if the mod that added
# the climing thing didn't provide its own list.
biome_lib_default_ground_nodes (List of default root nodes) string default:dirt_with_grass
# biome_lib divides its workload into "actions", as dictated by the sum
# total of all mods that use it, and this sets how much of that work is done
# per globalstep tick. If positive, a single action is executed on that
# percentage of ticks, on average. If negative, it becomes positive, and
# that many actions are executed on every single tick, skipping none.
# More negative means more throughput, at the expense of lag. On fast PC's,
# a setting of between -500 and -2000 might be good.
biome_lib_queue_ratio (Queue run ratio) int -200
# Minetest's map generator allows neighboring areas to overflow into one
# another, to create smooth terrain, but it often hands the map blocks that
# comprise those areas to Lua (and hence, to biome_lib) before that overflow
# function happens, which causes the mapgen to overwrite whatever Lua does
# to them. This setting (in seconds) makes biome_lib wait before adding its
# normal output to those map blocks, to give the engine plenty of time to
# run that overflow feature first.
biome_lib_block_timeout (Deferred block timeout) int 300
# This does just what it sounds like - it shows all debug output that's sent
# with a level equal to or greater than this value. A setting of 0 shows only
# the bare necessities, such as the startup and shutdown messages, 1 adds
# internal non-fatal errors to what's shown, 2 adds warnings, 3 adds other
# basic info, 4 adds all the verbose debugging spew. 3 is perhaps the most
# useful setting.
biome_lib_debug_log_level (Debug log level) int 0