2021-03-30 01:53:26 +02:00
-- Biome library mod by VanessaE
2015-08-09 16:15:16 +02:00
--
-- I got the temperature map idea from "hmmmm", values used for it came from
-- Splizard's snow mod.
--
2021-04-07 13:04:36 +02:00
biome_lib = { }
-- Boilerplate to support localized strings if intllib mod is installed.
local S
if minetest.global_exists ( " intllib " ) then
if intllib.make_gettext_pair then
S = intllib.make_gettext_pair ( )
else
S = intllib.Getter ( )
end
else
S = function ( s ) return s end
end
biome_lib.intllib = S
2015-08-09 16:15:16 +02:00
-- Various settings - most of these probably won't need to be changed
2018-10-25 16:04:26 +02:00
biome_lib.air = { name = " air " }
2015-08-09 16:15:16 +02:00
2021-04-06 07:07:29 +02:00
biome_lib.block_log = { }
2021-04-07 13:04:36 +02:00
biome_lib.block_recheck_list = { }
biome_lib.run_block_recheck_list = false
2015-08-09 16:15:16 +02:00
2021-04-06 07:07:29 +02:00
biome_lib.actionslist_aircheck = { }
biome_lib.actionslist_no_aircheck = { }
2015-08-09 16:15:16 +02:00
biome_lib.surfaceslist_aircheck = { }
biome_lib.surfaceslist_no_aircheck = { }
biome_lib.modpath = minetest.get_modpath ( " biome_lib " )
2021-03-30 03:10:41 +02:00
local function tableize ( s )
return string.split ( string.trim ( string.gsub ( s , " " , " " ) ) )
end
2021-04-05 17:21:22 +02:00
local c1 = minetest.settings : get ( " biome_lib_default_grow_through_nodes " )
2021-03-30 03:10:41 +02:00
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
2021-04-05 17:21:22 +02:00
local c2 = minetest.settings : get ( " biome_lib_default_water_nodes " )
2021-03-30 03:10:41 +02:00
biome_lib.default_water_nodes = { }
if c2 then
for _ , i in ipairs ( tableize ( c2 ) ) do
biome_lib.default_water_nodes [ i ] = true
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 " }
2021-04-07 13:30:58 +02:00
biome_lib.debug_log_level = tonumber ( minetest.settings : get ( " biome_lib_debug_log_level " ) ) or 0
2015-08-09 16:15:16 +02:00
2021-04-07 13:04:36 +02:00
local rr = tonumber ( minetest.settings : get ( " biome_lib_queue_run_ratio " ) ) or - 100
biome_lib.queue_run_ratio = 100 - rr
biome_lib.entries_per_step = math.max ( - rr , 1 )
2021-04-08 12:33:43 +02:00
-- 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
2021-04-09 19:04:28 +02:00
-- it takes a fraction of the block timeout interval.
2021-04-08 12:33:43 +02:00
2021-04-08 14:09:59 +02:00
local t = tonumber ( minetest.settings : get ( " biome_lib_block_timeout " ) ) or 300
2021-04-08 12:33:43 +02:00
biome_lib.block_timeout = t * 1000000
2021-04-09 19:04:28 +02:00
-- 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 ) )
2021-04-07 13:10:10 +02:00
2021-04-07 13:04:36 +02:00
local time_speed = tonumber ( minetest.settings : get ( " time_speed " ) )
2015-08-09 16:15:16 +02:00
2021-04-08 12:33:43 +02:00
biome_lib.plantlife_seed_diff = 329 -- needs to be global so other mods can see it
2015-08-09 16:15:16 +02:00
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
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
2021-04-07 13:04:36 +02:00
function biome_lib . dbg ( msg , level )
local l = tonumber ( level ) or 0
2021-04-07 13:30:58 +02:00
if biome_lib.debug_log_level >= l then
2021-04-07 13:04:36 +02:00
print ( " [Biome Lib] " .. msg )
minetest.log ( " verbose " , " [Biome Lib] " .. msg )
end
end
2019-01-02 23:08:07 +01:00
local function get_biome_data ( pos , perlin_fertile )
2020-04-15 22:59:40 +02:00
local fertility = perlin_fertile : get_2d ( { x = pos.x , y = pos.z } )
2019-01-02 23:08:07 +01:00
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
local temperature = biome_lib.perlin_temperature : get2d ( { x = pos.x , y = pos.z } )
local humidity = biome_lib.perlin_humidity : get2d ( { x = pos.x + 150 , y = pos.z + 50 } )
return fertility , temperature , humidity
end
2015-08-09 16:15:16 +02:00
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
2021-04-01 14:16:57 +02:00
biome.max_count = biome.max_count or 125
2015-08-09 16:15:16 +02:00
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
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Warning: Ignored registration for undefined spawn node: " .. dump ( nodes_or_function_or_model ) , 2 )
2015-08-09 16:15:16 +02:00
return
end
if type ( nodes_or_function_or_model ) == " string "
and not string.find ( nodes_or_function_or_model , " : " ) then
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Warning: Registered function call using deprecated string method: " .. dump ( nodes_or_function_or_model ) , 2 )
2015-08-09 16:15:16 +02:00
end
if biomedef.check_air == false then
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Register no-air-check mapgen hook: " .. dump ( nodes_or_function_or_model ) , 3 )
2015-08-09 16:15:16 +02:00
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
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Warning: Ignored no-air-check registration for undefined surface node: " .. dump ( s ) , 2 )
2015-08-09 16:15:16 +02:00
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
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Warning: Ignored no-air-check registration for undefined surface node: " .. dump ( s ) , 2 )
2015-08-09 16:15:16 +02:00
end
end
end
else
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Register with-air-checking mapgen hook: " .. dump ( nodes_or_function_or_model ) , 3 )
2015-08-09 16:15:16 +02:00
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
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Warning: Ignored with-air-checking registration for undefined surface node: " .. dump ( s ) , 2 )
2015-08-09 16:15:16 +02:00
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
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Warning: Ignored with-air-checking registration for undefined surface node: " .. dump ( s ) , 2 )
2015-08-09 16:15:16 +02:00
end
end
end
end
end
2019-01-02 20:35:39 +01:00
-- Function to check whether a position matches the given biome definition
-- Returns true when the surface can be populated
2019-01-02 22:06:07 +01:00
local function populate_single_surface ( biome , pos , perlin_fertile_area , checkair )
2019-01-02 20:35:39 +01:00
local p_top = { x = pos.x , y = pos.y + 1 , z = pos.z }
if math.random ( 1 , 100 ) <= biome.rarity then
return
end
2019-01-02 23:08:07 +01:00
local fertility , temperature , humidity = get_biome_data ( pos , perlin_fertile_area )
2019-01-02 20:35:39 +01:00
local pos_biome_ok = pos.y >= biome.min_elevation and pos.y <= biome.max_elevation
2019-01-02 23:08:07 +01:00
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
2019-01-02 20:35:39 +01:00
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
2021-04-06 07:07:29 +02:00
function biome_lib . populate_surfaces ( biome , nodes_or_function_or_model , snodes , checkair )
2021-04-01 13:18:28 +02:00
local items_added = 0
2015-08-09 16:15:16 +02:00
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
2019-01-02 20:35:39 +01:00
local pos = vector.new ( snodes [ i ] )
2019-01-02 22:06:07 +01:00
if populate_single_surface ( biome , pos , perlin_fertile_area , checkair ) then
2015-08-09 16:15:16 +02:00
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
2019-01-02 20:35:39 +01:00
if num_in_biome_nodes == 0 then
2021-04-01 13:18:28 +02:00
return 0
2019-01-02 20:35:39 +01:00
end
2015-08-09 16:15:16 +02:00
2021-04-01 15:02:41 +02:00
for i = 1 , math.min ( math.ceil ( biome.max_count / 25 ) , num_in_biome_nodes ) do
2019-01-02 20:35:39 +01:00
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
2015-08-09 16:15:16 +02:00
2019-01-02 20:35:39 +01:00
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 )
2018-10-25 16:04:26 +02:00
2019-01-02 20:35:39 +01:00
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
2015-08-09 16:15:16 +02:00
2019-01-02 20:35:39 +01:00
if biome.spawn_replace_node then
minetest.swap_node ( pos , biome_lib.air )
end
2015-08-09 16:15:16 +02:00
2019-01-02 20:35:39 +01:00
local objtype = type ( nodes_or_function_or_model )
2015-08-09 16:15:16 +02:00
2019-01-02 20:35:39 +01:00
if objtype == " table " then
if nodes_or_function_or_model.axiom then
biome_lib : generate_tree ( p_top , nodes_or_function_or_model )
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " An L-tree was spawned at " .. minetest.pos_to_string ( p_top ) , 4 )
2019-01-02 20:35:39 +01:00
spawned = true
else
2015-08-09 16:15:16 +02:00
local fdir = nil
if biome.random_facedir then
fdir = math.random ( biome.random_facedir [ 1 ] , biome.random_facedir [ 2 ] )
end
2021-04-06 08:57:36 +02:00
local n = nodes_or_function_or_model [ math.random ( # nodes_or_function_or_model ) ]
minetest.swap_node ( p_top , { name = n , param2 = fdir } )
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Node \" " .. n .. " \" was randomly picked from a list and placed at " .. minetest.pos_to_string ( p_top ) , 4 )
2015-08-09 16:15:16 +02:00
spawned = true
end
2019-01-02 20:35:39 +01:00
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 } )
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Node \" " .. nodes_or_function_or_model .. " \" was placed at " .. minetest.pos_to_string ( p_top ) , 4 )
2019-01-02 20:35:39 +01:00
spawned = true
elseif objtype == " function " then
nodes_or_function_or_model ( pos )
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " A function was run on surface node at " .. minetest.pos_to_string ( pos ) , 4 )
2019-01-02 20:35:39 +01:00
spawned = true
elseif objtype == " string " and pcall ( loadstring ( ( " return %s(...) " ) :
format ( nodes_or_function_or_model ) ) , pos ) then
spawned = true
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " An obsolete string-specified function was run on surface node at " .. minetest.pos_to_string ( p_top ) , 4 )
2015-08-09 16:15:16 +02:00
else
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Warning: Ignored invalid definition for object " .. dump ( nodes_or_function_or_model ) .. " that was pointed at { " .. dump ( pos ) .. " } " , 2 )
2015-08-09 16:15:16 +02:00
end
2019-01-02 20:35:39 +01:00
else
tries = tries + 1
2015-08-09 16:15:16 +02:00
end
end
2021-04-01 13:18:28 +02:00
if spawned then items_added = items_added + 1 end
2015-08-09 16:15:16 +02:00
end
2021-04-01 13:18:28 +02:00
return items_added
2015-08-09 16:15:16 +02:00
end
2021-04-06 07:07:29 +02:00
-- Primary log read-out/mapgen spawner
2021-04-06 18:37:47 +02:00
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
2021-04-06 19:38:43 +02:00
local n = minetest.get_node_or_nil ( { x = p.x + x , y = p.y + y , z = p.z + z } )
2021-04-06 18:37:47 +02:00
if not n or n.name == " ignore " then return false end
end
end
end
return true
end
function biome_lib . generate_block ( shutting_down )
2021-04-06 07:07:29 +02:00
2021-04-07 07:44:31 +02:00
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 ]
2021-04-06 07:07:29 +02:00
local pos_hash = minetest.hash_node_position ( minp )
if not biome_lib.pos_hash then -- we need to read the maplock and get the surfaces list
2021-04-07 13:10:10 +02:00
local now = minetest.get_us_time ( )
2021-04-06 07:07:29 +02:00
biome_lib.pos_hash = { }
2021-04-07 10:55:57 +02:00
minetest.load_area ( minp )
2021-04-07 07:44:31 +02:00
if not confirm_block_surroundings ( minp )
2021-04-07 13:10:10 +02:00
and not shutting_down
and ( blocklog [ 1 ] [ 4 ] + biome_lib.block_timeout ) > now then -- if any neighbors appear not to be loaded and the block hasn't expired yet, defer it
2021-04-09 19:04:28 +02:00
2021-04-07 07:44:31 +02:00
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
2021-04-06 18:37:47 +02:00
biome_lib.pos_hash = nil
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Mapblock at " .. minetest.pos_to_string ( minp ) ..
" had a neighbor not fully emerged, skipped it for now. " , 4 )
2021-04-06 18:37:47 +02:00
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 )
biome_lib.pos_hash . action_index = 1
if # biome_lib.pos_hash . surface_node_list > 0 then
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Mapblock at " .. minetest.pos_to_string ( minp ) ..
2021-04-06 18:37:47 +02:00
" has " .. # biome_lib.pos_hash . surface_node_list ..
2021-04-07 11:16:11 +02:00
" surface nodes to work on (airflag= " .. dump ( airflag ) .. " ) " , 4 )
2021-04-06 18:37:47 +02:00
end
2021-04-01 13:18:28 +02:00
end
2021-04-06 07:07:29 +02:00
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
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " Deleted mapblock " .. minetest.pos_to_string ( minp ) .. " from the block log " , 4 )
2021-04-06 07:07:29 +02:00
end
2021-04-07 07:44:31 +02:00
table.remove ( blocklog , 1 )
2021-04-06 07:07:29 +02:00
biome_lib.pos_hash = nil
2019-01-02 20:35:39 +01:00
else
2021-04-06 07:07:29 +02:00
-- 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
2021-04-01 13:18:28 +02:00
end
2015-08-09 16:15:16 +02:00
else
2021-04-06 07:07:29 +02:00
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
2015-08-09 16:15:16 +02:00
end
2021-04-06 07:07:29 +02:00
if added > 0 then
2021-04-07 11:16:11 +02:00
biome_lib.dbg ( " biome_lib.populate_surfaces ran on mapblock at " ..
2021-04-06 07:07:29 +02:00
minetest.pos_to_string ( minp ) .. " . Entry # " ..
2021-04-07 11:16:11 +02:00
( biome_lib.pos_hash . action_index - 1 ) .. " added " .. added .. " items. " , 4 )
2015-08-09 16:15:16 +02:00
end
end
end
-- "Play" them back, populating them with new stuff in the process
minetest.register_globalstep ( function ( dtime )
2021-04-06 19:08:43 +02:00
if not biome_lib.block_log [ 1 ] then return end -- the block log is empty
2021-04-06 16:00:16 +02:00
if math.random ( 100 ) > biome_lib.queue_run_ratio then return end
2021-04-06 07:07:29 +02:00
for s = 1 , biome_lib.entries_per_step do
biome_lib.generate_block ( )
2015-08-09 16:15:16 +02:00
end
end )
2021-04-08 12:33:43 +02:00
-- 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 ( )
2021-04-09 19:04:28 +02:00
if # biome_lib.block_recheck_list > 1
2021-04-08 12:33:43 +02:00
and # biome_lib.block_log == 0 then
2021-04-09 19:04:28 +02:00
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
2021-04-08 12:33:43 +02:00
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 ( )
2015-08-09 16:15:16 +02:00
-- Play out the entire log all at once on shutdown
-- to prevent unpopulated map areas
2021-04-09 18:17:39 +02:00
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
2015-08-09 16:15:16 +02:00
minetest.register_on_shutdown ( function ( )
2021-04-09 18:17:39 +02:00
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
2019-01-02 20:35:39 +01:00
return
end
2021-04-09 18:17:39 +02:00
biome_lib.dbg ( " Stand by, purging the mapblock log " ..
" (there are " .. ( # biome_lib.block_log + # biome_lib.block_recheck_list ) .. " entries) ... " , 0 )
2021-04-06 07:07:29 +02:00
while # biome_lib.block_log > 0 do
2021-04-06 18:37:47 +02:00
biome_lib.generate_block ( true )
2021-04-09 18:17:39 +02:00
biome_lib.check_remaining_time ( )
2015-08-09 16:15:16 +02:00
end
2021-04-06 22:07:49 +02:00
if # biome_lib.block_recheck_list > 0 then
biome_lib.block_log = table.copy ( biome_lib.block_recheck_list )
while # biome_lib.block_log > 0 do
biome_lib.generate_block ( true )
2021-04-09 18:17:39 +02:00
biome_lib.check_remaining_time ( )
2021-04-06 22:07:49 +02:00
end
end
2021-04-09 18:17:39 +02:00
biome_lib.dbg ( " Log purge completed after " ..
format_time ( minetest.get_us_time ( ) - biome_lib.shutdown_start_time ) .. " . " , 0 )
2015-08-09 16:15:16 +02:00
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 )
2018-12-01 16:51:18 +01:00
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
2015-08-09 16:15:16 +02:00
minetest.register_abm ( {
nodenames = biome.spawn_surfaces ,
interval = biome.interval ,
chance = biome.spawn_chance ,
neighbors = biome.neighbors ,
2018-12-01 16:51:18 +01:00
label = biome.label ,
2015-08-09 16:15:16 +02:00
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 )
2019-01-02 23:08:07 +01:00
local fertility , temperature , humidity = get_biome_data ( pos , perlin_fertile_area )
2019-01-02 22:29:07 +01:00
local pos_biome_ok = pos.y >= biome.min_elevation and pos.y <= biome.max_elevation
2019-01-02 23:08:07 +01:00
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
2019-01-02 22:29:07 +01:00
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
2021-03-30 03:10:41 +02:00
if biome_lib.default_water_nodes [ currentsurface ] and
2019-01-02 22:29:07 +01:00
# minetest.find_nodes_in_area (
{ x = pos.x , y = pos.y - biome.depth_max - 1 , z = pos.z } ,
2019-01-07 19:37:16 +01:00
vector.new ( pos ) ,
2021-03-30 03:10:41 +02:00
biome_lib.default_wet_surfaces
2019-01-02 22:29:07 +01:00
) == 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 } )
2015-08-09 16:15:16 +02:00
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
2018-10-25 16:04:26 +02:00
minetest.swap_node ( pos , biome_lib.air )
2015-08-09 16:15:16 +02:00
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 )
2019-01-02 23:08:07 +01:00
local fertility , temperature , _ = get_biome_data ( pos , perlin_fertile_area )
grow_function ( pos , fertility , temperature , walldir )
2015-08-09 16:15:16 +02:00
return
elseif growtype == " string " then
local perlin_fertile_area = minetest.get_perlin ( seeddiff , perlin_octaves , perlin_persistence , perlin_scale )
2019-01-02 23:08:07 +01:00
local fertility , temperature , _ = get_biome_data ( pos , perlin_fertile_area )
assert ( loadstring ( grow_function .. " (...) " ) ) ( pos , fertility , temperature , walldir )
2015-08-09 16:15:16 +02:00
return
elseif growtype == " nil " then
2018-10-25 15:45:51 +02:00
minetest.swap_node ( pos , { name = replacement , param2 = walldir } )
2015-08-09 16:15:16 +02:00
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
2019-01-02 20:35:39 +01:00
dofile ( biome_lib.modpath .. " /search_functions.lua " )
2019-01-02 22:06:07 +01:00
assert ( loadfile ( biome_lib.modpath .. " /growth.lua " ) ) ( time_scale )
2015-08-09 16:15:16 +02:00
-- Check for infinite stacks
2017-05-13 11:09:20 +02:00
if minetest.get_modpath ( " unified_inventory " ) or not minetest.settings : get_bool ( " creative_mode " ) then
2015-08-09 16:15:16 +02:00
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
2021-04-07 13:30:58 +02:00
if biome_lib.debug_log_level >= 3 then
2021-04-06 07:07:29 +02:00
biome_lib.last_count = 0
2021-04-01 10:39:35 +02:00
2021-04-06 07:07:29 +02:00
function biome_lib . show_pending_block_count ( )
if biome_lib.last_count ~= # biome_lib.block_log then
2021-04-08 12:33:43 +02:00
biome_lib.dbg ( string.format ( " Pending block counts - ready to process: %-8icurrently deferred: %i " ,
# biome_lib.block_log , # biome_lib.block_recheck_list ) , 3 )
2021-04-06 07:07:29 +02:00
biome_lib.last_count = # biome_lib.block_log
2021-04-07 11:26:31 +02:00
biome_lib.queue_idle_flag = false
elseif not biome_lib.queue_idle_flag then
2021-04-08 12:33:43 +02:00
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
2021-04-07 11:26:31 +02:00
biome_lib.queue_idle_flag = true
2021-04-01 10:39:35 +02:00
end
2021-04-06 07:07:29 +02:00
minetest.after ( 1 , biome_lib.show_pending_block_count )
2021-04-01 10:39:35 +02:00
end
2021-04-06 07:07:29 +02:00
biome_lib.show_pending_block_count ( )
2021-04-01 10:39:35 +02:00
end
2021-04-07 11:16:11 +02:00
minetest.after ( 0 , function ( )
biome_lib.dbg ( " Registered a total of " .. ( # biome_lib.surfaceslist_aircheck ) + ( # biome_lib.surfaceslist_no_aircheck ) .. " surface types to be evaluated, spread " , 0 )
biome_lib.dbg ( " across " .. # biome_lib.actionslist_aircheck .. " actions with air-checking and " .. # biome_lib.actionslist_no_aircheck .. " actions without. " , 0 )
end )
biome_lib.dbg ( " [Biome Lib] Loaded " , 0 )