2020-03-30 22:41:58 +02:00
-- Plantlife library mod by Vanessa Ezekowitz
--
-- License: WTFPL
--
-- I got the temperature map idea from "hmmmm", values used for it came from
-- Splizard's snow mod.
--
-- Various settings - most of these probably won't need to be changed
plantslib = { }
plantslib.blocklist_aircheck = { }
plantslib.blocklist_no_aircheck = { }
plantslib.surface_nodes_aircheck = { }
plantslib.surface_nodes_no_aircheck = { }
plantslib.surfaceslist_aircheck = { }
plantslib.surfaceslist_no_aircheck = { }
plantslib.actioncount_aircheck = { }
plantslib.actioncount_no_aircheck = { }
plantslib.actionslist_aircheck = { }
plantslib.actionslist_no_aircheck = { }
plantslib.modpath = minetest.get_modpath ( " plants_lib " )
plantslib.total_no_aircheck_calls = 0
-- Boilerplate to support localized strings if intllib mod is installed.
local S
if minetest.get_modpath ( " intllib " ) then
S = intllib.Getter ( )
else
S = function ( s ) return s end
end
plantslib.intllib = S
local DEBUG = false --... except if you want to spam the console with debugging info :-)
function plantslib : dbg ( msg )
if DEBUG then
minetest.log ( " verbose " , " [Plantlife] " .. msg )
end
end
plantslib.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
2020-03-30 22:43:46 +02:00
local time_speed = tonumber ( minetest.settings : get ( " time_speed " ) )
2020-03-30 22:41:58 +02:00
if time_speed and time_speed > 0 then
time_scale = 72 / time_speed
end
--PerlinNoise(seed, octaves, persistence, scale)
plantslib.perlin_temperature = PerlinNoise ( temperature_seeddiff , temperature_octaves , temperature_persistence , temperature_scale )
plantslib.perlin_humidity = PerlinNoise ( humidity_seeddiff , humidity_octaves , humidity_persistence , humidity_scale )
-- Local functions
function plantslib : 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 plantslib : 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 plantslib : 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
plantslib : 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
plantslib : dbg ( " Warning: Registered function call using deprecated string method: " .. dump ( nodes_or_function_or_model ) )
end
if biomedef.check_air == false then
plantslib : dbg ( " Register no-air-check mapgen hook: " .. dump ( nodes_or_function_or_model ) )
plantslib.actionslist_no_aircheck [ # plantslib.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 ( plantslib.surfaceslist_no_aircheck , s ) then
plantslib.surfaceslist_no_aircheck [ # plantslib.surfaceslist_no_aircheck + 1 ] = s
end
else
plantslib : 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 ( plantslib.surfaceslist_no_aircheck , s ) then
plantslib.surfaceslist_no_aircheck [ # plantslib.surfaceslist_no_aircheck + 1 ] = s
end
else
plantslib : dbg ( " Warning: Ignored no-air-check registration for undefined surface node: " .. dump ( s ) )
end
end
end
else
plantslib : dbg ( " Register with-air-checking mapgen hook: " .. dump ( nodes_or_function_or_model ) )
plantslib.actionslist_aircheck [ # plantslib.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 ( plantslib.surfaceslist_aircheck , s ) then
plantslib.surfaceslist_aircheck [ # plantslib.surfaceslist_aircheck + 1 ] = s
end
else
plantslib : 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 ( plantslib.surfaceslist_aircheck , s ) then
plantslib.surfaceslist_aircheck [ # plantslib.surfaceslist_aircheck + 1 ] = s
end
else
plantslib : dbg ( " Warning: Ignored with-air-checking registration for undefined surface node: " .. dump ( s ) )
end
end
end
end
end
function plantslib : populate_surfaces ( biome , nodes_or_function_or_model , snodes , checkair )
plantslib : set_defaults ( biome )
-- filter stage 1 - find nodes from the supplied surfaces that are within the current biome.
local in_biome_nodes = { }
local perlin_fertile_area = minetest.get_perlin ( biome.seed_diff , perlin_octaves , perlin_persistence , perlin_scale )
for i = 1 , # snodes do
local pos = snodes [ i ]
local p_top = { x = pos.x , y = pos.y + 1 , z = pos.z }
2020-03-30 22:43:46 +02:00
local noise1 = perlin_fertile_area : get_2d ( { x = pos.x , y = pos.z } )
local noise2 = plantslib.perlin_temperature : get_2d ( { x = pos.x , y = pos.z } )
local noise3 = plantslib.perlin_humidity : get_2d ( { x = pos.x + 150 , y = pos.z + 50 } )
2020-03-30 22:41:58 +02:00
local biome_surfaces_string = dump ( biome.surface )
local surface_ok = false
if not biome.depth then
local dest_node = minetest.get_node ( pos )
if string.find ( biome_surfaces_string , dest_node.name ) then
surface_ok = true
else
if string.find ( biome_surfaces_string , " group: " ) then
for j = 1 , # biome.surface do
if string.find ( biome.surface [ j ] , " ^group: " )
and minetest.get_item_group ( dest_node.name , biome.surface [ j ] ) then
surface_ok = true
break
end
end
end
end
elseif not string.find ( biome_surfaces_string , minetest.get_node ( { x = pos.x , y = pos.y - biome.depth - 1 , z = pos.z } ) . name ) then
surface_ok = true
end
if surface_ok
and ( not checkair or minetest.get_node ( p_top ) . name == " air " )
and pos.y >= biome.min_elevation
and pos.y <= biome.max_elevation
and noise1 > biome.plantlife_limit
and noise2 <= biome.temp_min
and noise2 >= biome.temp_max
and noise3 <= biome.humidity_min
and noise3 >= biome.humidity_max
and ( not biome.ncount or # ( minetest.find_nodes_in_area ( { x = pos.x - 1 , y = pos.y , z = pos.z - 1 } , { x = pos.x + 1 , y = pos.y , z = pos.z + 1 } , biome.neighbors ) ) > biome.ncount )
and ( not biome.near_nodes or # ( minetest.find_nodes_in_area ( { x = pos.x - biome.near_nodes_size , y = pos.y - biome.near_nodes_vertical , z = pos.z - biome.near_nodes_size } , { x = pos.x + biome.near_nodes_size , y = pos.y + biome.near_nodes_vertical , z = pos.z + biome.near_nodes_size } , biome.near_nodes ) ) >= biome.near_nodes_count )
and math.random ( 1 , 100 ) > biome.rarity
and ( not biome.below_nodes or string.find ( dump ( biome.below_nodes ) , minetest.get_node ( { x = pos.x , y = pos.y - 1 , z = pos.z } ) . name ) )
then
in_biome_nodes [ # in_biome_nodes + 1 ] = pos
end
end
-- filter stage 2 - find places within that biome area to place the plants.
local num_in_biome_nodes = # in_biome_nodes
if num_in_biome_nodes > 0 then
for i = 1 , math.min ( biome.max_count , num_in_biome_nodes ) do
local tries = 0
local spawned = false
while tries < 2 and not spawned do
local pos = in_biome_nodes [ math.random ( 1 , num_in_biome_nodes ) ]
if biome.spawn_replace_node then
pos.y = pos.y - 1
end
local p_top = { x = pos.x , y = pos.y + 1 , z = pos.z }
if not ( biome.avoid_nodes and biome.avoid_radius and minetest.find_node_near ( p_top , biome.avoid_radius + math.random ( - 1.5 , 2 ) , biome.avoid_nodes ) ) then
if biome.delete_above then
minetest.remove_node ( p_top )
minetest.remove_node ( { x = p_top.x , y = p_top.y + 1 , z = p_top.z } )
end
if biome.delete_above_surround then
minetest.remove_node ( { x = p_top.x - 1 , y = p_top.y , z = p_top.z } )
minetest.remove_node ( { x = p_top.x + 1 , y = p_top.y , z = p_top.z } )
minetest.remove_node ( { x = p_top.x , y = p_top.y , z = p_top.z - 1 } )
minetest.remove_node ( { x = p_top.x , y = p_top.y , z = p_top.z + 1 } )
minetest.remove_node ( { x = p_top.x - 1 , y = p_top.y + 1 , z = p_top.z } )
minetest.remove_node ( { x = p_top.x + 1 , y = p_top.y + 1 , z = p_top.z } )
minetest.remove_node ( { x = p_top.x , y = p_top.y + 1 , z = p_top.z - 1 } )
minetest.remove_node ( { x = p_top.x , y = p_top.y + 1 , z = p_top.z + 1 } )
end
if biome.spawn_replace_node then
minetest.remove_node ( pos )
end
local objtype = type ( nodes_or_function_or_model )
if objtype == " table " then
if nodes_or_function_or_model.axiom then
plantslib : generate_tree ( pos , nodes_or_function_or_model )
spawned = true
else
local fdir = nil
if biome.random_facedir then
fdir = math.random ( biome.random_facedir [ 1 ] , biome.random_facedir [ 2 ] )
end
minetest.set_node ( p_top , { name = nodes_or_function_or_model [ math.random ( # nodes_or_function_or_model ) ] , param2 = fdir } )
spawned = true
end
elseif objtype == " string " and
minetest.registered_nodes [ nodes_or_function_or_model ] then
local fdir = nil
if biome.random_facedir then
fdir = math.random ( biome.random_facedir [ 1 ] , biome.random_facedir [ 2 ] )
end
minetest.set_node ( p_top , { name = nodes_or_function_or_model , param2 = fdir } )
spawned = true
elseif objtype == " function " then
nodes_or_function_or_model ( pos )
spawned = true
elseif objtype == " string " and pcall ( loadstring ( ( " return %s(...) " ) :
format ( nodes_or_function_or_model ) ) , pos ) then
spawned = true
else
plantslib : dbg ( " Warning: Ignored invalid definition for object " .. dump ( nodes_or_function_or_model ) .. " that was pointed at { " .. dump ( pos ) .. " } " )
end
else
tries = tries + 1
end
end
end
end
end
-- Primary mapgen spawner, for mods that can work with air checking enabled on
-- a surface during the initial map read stage.
function plantslib : generate_block_with_air_checking ( )
if # plantslib.blocklist_aircheck > 0 then
local minp = plantslib.blocklist_aircheck [ 1 ] [ 1 ]
local maxp = plantslib.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 plantslib.surface_nodes_aircheck . blockhash then
if type ( minetest.find_nodes_in_area_under_air ) == " function " then -- use newer API call
plantslib.surface_nodes_aircheck . blockhash =
minetest.find_nodes_in_area_under_air ( minp , maxp , plantslib.surfaceslist_aircheck )
else
local search_area = minetest.find_nodes_in_area ( minp , maxp , plantslib.surfaceslist_aircheck )
-- search the generated block for air-bounded surfaces the slow way.
plantslib.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
plantslib.surface_nodes_aircheck . blockhash [ # plantslib.surface_nodes_aircheck . blockhash + 1 ] = pos
end
end
end
plantslib.actioncount_aircheck . blockhash = 1
else
if plantslib.actioncount_aircheck . blockhash <= # plantslib.actionslist_aircheck then
-- [1] is biome, [2] is node/function/model
plantslib : populate_surfaces (
plantslib.actionslist_aircheck [ plantslib.actioncount_aircheck . blockhash ] [ 1 ] ,
plantslib.actionslist_aircheck [ plantslib.actioncount_aircheck . blockhash ] [ 2 ] ,
plantslib.surface_nodes_aircheck . blockhash , true )
plantslib.actioncount_aircheck . blockhash = plantslib.actioncount_aircheck . blockhash + 1
else
if plantslib.surface_nodes_aircheck . blockhash then
table.remove ( plantslib.blocklist_aircheck , 1 )
plantslib.surface_nodes_aircheck . blockhash = nil
end
end
end
end
end
-- Secondary mapgen spawner, for mods that require disabling of
-- checking for air during the initial map read stage.
function plantslib : generate_block_no_aircheck ( )
if # plantslib.blocklist_no_aircheck > 0 then
local minp = plantslib.blocklist_no_aircheck [ 1 ] [ 1 ]
local maxp = plantslib.blocklist_no_aircheck [ 1 ] [ 2 ]
local blockhash = minetest.hash_node_position ( minp )
if not plantslib.surface_nodes_no_aircheck . blockhash then
-- directly read the block to be searched into the chunk cache
plantslib.surface_nodes_no_aircheck . blockhash =
minetest.find_nodes_in_area ( minp , maxp , plantslib.surfaceslist_no_aircheck )
plantslib.actioncount_no_aircheck . blockhash = 1
else
if plantslib.actioncount_no_aircheck . blockhash <= # plantslib.actionslist_no_aircheck then
plantslib : populate_surfaces (
plantslib.actionslist_no_aircheck [ plantslib.actioncount_no_aircheck . blockhash ] [ 1 ] ,
plantslib.actionslist_no_aircheck [ plantslib.actioncount_no_aircheck . blockhash ] [ 2 ] ,
plantslib.surface_nodes_no_aircheck . blockhash , false )
plantslib.actioncount_no_aircheck . blockhash = plantslib.actioncount_no_aircheck . blockhash + 1
else
if plantslib.surface_nodes_no_aircheck . blockhash then
table.remove ( plantslib.blocklist_no_aircheck , 1 )
plantslib.surface_nodes_no_aircheck . blockhash = nil
end
end
end
end
end
-- "Record" the chunks being generated by the core mapgen
minetest.register_on_generated ( function ( minp , maxp , blockseed )
plantslib.blocklist_aircheck [ # plantslib.blocklist_aircheck + 1 ] = { minp , maxp }
end )
minetest.register_on_generated ( function ( minp , maxp , blockseed )
plantslib.blocklist_no_aircheck [ # plantslib.blocklist_no_aircheck + 1 ] = { minp , maxp }
end )
-- "Play" them back, populating them with new stuff in the process
minetest.register_globalstep ( function ( dtime )
if dtime < 0.2 and -- don't attempt to populate if lag is already too high
( # plantslib.blocklist_aircheck > 0 or # plantslib.blocklist_no_aircheck > 0 ) then
plantslib.globalstep_start_time = minetest.get_us_time ( )
plantslib.globalstep_runtime = 0
while ( # plantslib.blocklist_aircheck > 0 or # plantslib.blocklist_no_aircheck > 0 )
and plantslib.globalstep_runtime < 200000 do -- 0.2 seconds, in uS.
if # plantslib.blocklist_aircheck > 0 then
plantslib : generate_block_with_air_checking ( )
end
if # plantslib.blocklist_no_aircheck > 0 then
plantslib : generate_block_no_aircheck ( )
end
plantslib.globalstep_runtime = minetest.get_us_time ( ) - plantslib.globalstep_start_time
end
end
end )
-- Play out the entire log all at once on shutdown
-- to prevent unpopulated map areas
minetest.register_on_shutdown ( function ( )
2020-03-30 23:09:30 +02:00
minetest.log ( " verbose " , " [plants_lib] Stand by, playing out the rest of the aircheck mapblock log " )
minetest.log ( " verbose " , " (there are " .. # plantslib.blocklist_aircheck .. " entries)... " )
2020-03-30 22:41:58 +02:00
while true do
plantslib : generate_block_with_air_checking ( 0.1 )
if # plantslib.blocklist_aircheck == 0 then return end
end
end )
minetest.register_on_shutdown ( function ( )
2020-03-30 23:09:30 +02:00
minetest.log ( " verbose " , " [plants_lib] Stand by, playing out the rest of the no-aircheck mapblock log " )
minetest.log ( " verbose " , " (there are " .. # plantslib.blocklist_aircheck .. " entries)... " )
2020-03-30 22:41:58 +02:00
while true do
plantslib : generate_block_no_aircheck ( 0.1 )
if # plantslib.blocklist_no_aircheck == 0 then return end
end
end )
-- The spawning ABM
function plantslib : 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
plantslib : set_defaults ( biome )
biome.spawn_plants_count = # ( biome.spawn_plants )
minetest.register_abm ( {
nodenames = biome.spawn_surfaces ,
interval = biome.interval ,
chance = biome.spawn_chance ,
neighbors = biome.neighbors ,
action = function ( pos , node , active_object_count , active_object_count_wider )
local p_top = { x = pos.x , y = pos.y + 1 , z = pos.z }
local n_top = minetest.get_node ( p_top )
local perlin_fertile_area = minetest.get_perlin ( biome.seed_diff , perlin_octaves , perlin_persistence , perlin_scale )
2020-03-30 22:43:46 +02:00
local noise1 = perlin_fertile_area : get_2d ( { x = p_top.x , y = p_top.z } )
local noise2 = plantslib.perlin_temperature : get_2d ( { x = p_top.x , y = p_top.z } )
local noise3 = plantslib.perlin_humidity : get_2d ( { x = p_top.x + 150 , y = p_top.z + 50 } )
2020-03-30 22:41:58 +02:00
if noise1 > biome.plantlife_limit
and noise2 <= biome.temp_min
and noise2 >= biome.temp_max
and noise3 <= biome.humidity_min
and noise3 >= biome.humidity_max
and plantslib : is_node_loaded ( p_top ) then
local n_light = minetest.get_node_light ( p_top , nil )
if not ( biome.avoid_nodes and biome.avoid_radius and minetest.find_node_near ( p_top , biome.avoid_radius + math.random ( - 1.5 , 2 ) , biome.avoid_nodes ) )
and n_light >= biome.light_min
and n_light <= biome.light_max
and ( not ( biome.neighbors and biome.ncount ) or # ( minetest.find_nodes_in_area ( { x = pos.x - 1 , y = pos.y , z = pos.z - 1 } , { x = pos.x + 1 , y = pos.y , z = pos.z + 1 } , biome.neighbors ) ) > biome.ncount )
and ( not ( biome.near_nodes and biome.near_nodes_count and biome.near_nodes_size ) or # ( minetest.find_nodes_in_area ( { x = pos.x - biome.near_nodes_size , y = pos.y - biome.near_nodes_vertical , z = pos.z - biome.near_nodes_size } , { x = pos.x + biome.near_nodes_size , y = pos.y + biome.near_nodes_vertical , z = pos.z + biome.near_nodes_size } , biome.near_nodes ) ) >= biome.near_nodes_count )
and ( not ( biome.air_count and biome.air_size ) or # ( minetest.find_nodes_in_area ( { x = p_top.x - biome.air_size , y = p_top.y , z = p_top.z - biome.air_size } , { x = p_top.x + biome.air_size , y = p_top.y , z = p_top.z + biome.air_size } , " air " ) ) >= biome.air_count )
and pos.y >= biome.min_elevation
and pos.y <= biome.max_elevation
then
local walldir = plantslib : find_adjacent_wall ( p_top , biome.verticals_list , biome.choose_random_wall )
if biome.alt_wallnode and walldir then
if n_top.name == " air " then
minetest.set_node ( p_top , { name = biome.alt_wallnode , param2 = walldir } )
end
else
local currentsurface = minetest.get_node ( pos ) . name
if currentsurface ~= " default:water_source "
or ( currentsurface == " default:water_source " and # ( minetest.find_nodes_in_area ( { x = pos.x , y = pos.y - biome.depth_max - 1 , z = pos.z } , { x = pos.x , y = pos.y , z = pos.z } , { " default:dirt " , " default:dirt_with_grass " , " default:sand " } ) ) > 0 )
then
local rnd = math.random ( 1 , biome.spawn_plants_count )
local plant_to_spawn = biome.spawn_plants [ rnd ]
local fdir = biome.facedir
if biome.random_facedir then
fdir = math.random ( biome.random_facedir [ 1 ] , biome.random_facedir [ 2 ] )
end
if type ( biome.spawn_plants ) == " string " then
assert ( loadstring ( biome.spawn_plants .. " (...) " ) ) ( pos )
elseif not biome.spawn_on_side and not biome.spawn_on_bottom and not biome.spawn_replace_node then
if n_top.name == " air " then
minetest.set_node ( p_top , { name = plant_to_spawn , param2 = fdir } )
end
elseif biome.spawn_replace_node then
minetest.set_node ( pos , { name = plant_to_spawn , param2 = fdir } )
elseif biome.spawn_on_side then
local onside = plantslib : find_open_side ( pos )
if onside then
minetest.set_node ( onside.newpos , { name = plant_to_spawn , param2 = onside.facedir } )
end
elseif biome.spawn_on_bottom then
if minetest.get_node ( { x = pos.x , y = pos.y - 1 , z = pos.z } ) . name == " air " then
minetest.set_node ( { x = pos.x , y = pos.y - 1 , z = pos.z } , { name = plant_to_spawn , param2 = fdir } )
end
end
end
end
end
end
end
} )
end
-- The growing ABM
function plantslib : grow_plants ( opts )
local options = opts
options.height_limit = options.height_limit or 5
options.ground_nodes = options.ground_nodes or { " default:dirt_with_grass " }
options.grow_nodes = options.grow_nodes or { " default:dirt_with_grass " }
options.seed_diff = options.seed_diff or 0
if options.grow_delay * time_scale >= 1 then
options.interval = options.grow_delay * time_scale
else
options.interval = 1
end
minetest.register_abm ( {
nodenames = { options.grow_plant } ,
interval = options.interval ,
chance = options.grow_chance ,
action = function ( pos , node , active_object_count , active_object_count_wider )
local p_top = { x = pos.x , y = pos.y + 1 , z = pos.z }
local p_bot = { x = pos.x , y = pos.y - 1 , z = pos.z }
local n_top = minetest.get_node ( p_top )
local n_bot = minetest.get_node ( p_bot )
local root_node = minetest.get_node ( { x = pos.x , y = pos.y - options.height_limit , z = pos.z } )
local walldir = nil
if options.need_wall and options.verticals_list then
walldir = plantslib : find_adjacent_wall ( p_top , options.verticals_list , options.choose_random_wall )
end
if ( n_top.name == " air " or n_top.name == " default:snow " )
and ( not options.need_wall or ( options.need_wall and walldir ) ) then
-- corner case for changing short junglegrass
-- to dry shrub in desert
if n_bot.name == options.dry_early_node and options.grow_plant == " junglegrass:short " then
minetest.set_node ( pos , { name = " default:dry_shrub " } )
elseif options.grow_vertically and walldir then
if plantslib : search_downward ( pos , options.height_limit , options.ground_nodes ) then
minetest.set_node ( p_top , { name = options.grow_plant , param2 = walldir } )
end
elseif not options.grow_result and not options.grow_function then
minetest.remove_node ( pos )
else
plantslib : replace_object ( pos , options.grow_result , options.grow_function , options.facedir , options.seed_diff )
end
end
end
} )
end
-- Function to decide how to replace a plant - either grow it, replace it with
-- a tree, run a function, or die with an error.
function plantslib : replace_object ( pos , replacement , grow_function , walldir , seeddiff )
local growtype = type ( grow_function )
if growtype == " table " then
minetest.remove_node ( pos )
plantslib : grow_tree ( pos , grow_function )
return
elseif growtype == " function " then
local perlin_fertile_area = minetest.get_perlin ( seeddiff , perlin_octaves , perlin_persistence , perlin_scale )
2020-03-30 22:43:46 +02:00
local noise1 = perlin_fertile_area : get_2d ( { x = pos.x , y = pos.z } )
local noise2 = plantslib.perlin_temperature : get_2d ( { x = pos.x , y = pos.z } )
2020-03-30 22:41:58 +02:00
grow_function ( pos , noise1 , noise2 , walldir )
return
elseif growtype == " string " then
local perlin_fertile_area = minetest.get_perlin ( seeddiff , perlin_octaves , perlin_persistence , perlin_scale )
2020-03-30 22:43:46 +02:00
local noise1 = perlin_fertile_area : get_2d ( { x = pos.x , y = pos.z } )
local noise2 = plantslib.perlin_temperature : get_2d ( { x = pos.x , y = pos.z } )
2020-03-30 22:41:58 +02:00
assert ( loadstring ( grow_function .. " (...) " ) ) ( pos , noise1 , noise2 , walldir )
return
elseif growtype == " nil " then
minetest.set_node ( pos , { name = replacement , param2 = walldir } )
return
elseif growtype ~= " nil " and growtype ~= " string " and growtype ~= " table " then
error ( " Invalid grow function " .. dump ( grow_function ) .. " used on object at ( " .. dump ( pos ) .. " ) " )
end
end
-- function to decide if a node has a wall that's in verticals_list{}
-- returns wall direction of valid node, or nil if invalid.
function plantslib : find_adjacent_wall ( pos , verticals , randomflag )
local verts = dump ( verticals )
if randomflag then
local walltab = { }
if string.find ( verts , minetest.get_node ( { x = pos.x - 1 , y = pos.y , z = pos.z } ) . name ) then walltab [ # walltab + 1 ] = 3 end
if string.find ( verts , minetest.get_node ( { x = pos.x + 1 , y = pos.y , z = pos.z } ) . name ) then walltab [ # walltab + 1 ] = 2 end
if string.find ( verts , minetest.get_node ( { x = pos.x , y = pos.y , z = pos.z - 1 } ) . name ) then walltab [ # walltab + 1 ] = 5 end
if string.find ( verts , minetest.get_node ( { x = pos.x , y = pos.y , z = pos.z + 1 } ) . name ) then walltab [ # walltab + 1 ] = 4 end
if # walltab > 0 then return walltab [ math.random ( 1 , # walltab ) ] end
else
if string.find ( verts , minetest.get_node ( { x = pos.x - 1 , y = pos.y , z = pos.z } ) . name ) then return 3 end
if string.find ( verts , minetest.get_node ( { x = pos.x + 1 , y = pos.y , z = pos.z } ) . name ) then return 2 end
if string.find ( verts , minetest.get_node ( { x = pos.x , y = pos.y , z = pos.z - 1 } ) . name ) then return 5 end
if string.find ( verts , minetest.get_node ( { x = pos.x , y = pos.y , z = pos.z + 1 } ) . name ) then return 4 end
end
return nil
end
-- Function to search downward from the given position, looking for the first
-- node that matches the ground table. Returns the new position, or nil if
-- height limit is exceeded before finding it.
function plantslib : search_downward ( pos , heightlimit , ground )
for i = 0 , heightlimit do
if string.find ( dump ( ground ) , minetest.get_node ( { x = pos.x , y = pos.y - i , z = pos.z } ) . name ) then
return { x = pos.x , y = pos.y - i , z = pos.z }
end
end
return false
end
function plantslib : find_open_side ( pos )
if minetest.get_node ( { x = pos.x - 1 , y = pos.y , z = pos.z } ) . name == " air " then
return { newpos = { x = pos.x - 1 , y = pos.y , z = pos.z } , facedir = 2 }
end
if minetest.get_node ( { x = pos.x + 1 , y = pos.y , z = pos.z } ) . name == " air " then
return { newpos = { x = pos.x + 1 , y = pos.y , z = pos.z } , facedir = 3 }
end
if minetest.get_node ( { x = pos.x , y = pos.y , z = pos.z - 1 } ) . name == " air " then
return { newpos = { x = pos.x , y = pos.y , z = pos.z - 1 } , facedir = 4 }
end
if minetest.get_node ( { x = pos.x , y = pos.y , z = pos.z + 1 } ) . name == " air " then
return { newpos = { x = pos.x , y = pos.y , z = pos.z + 1 } , facedir = 5 }
end
return nil
end
-- spawn_tree() on generate is routed through here so that other mods can hook
-- into it.
function plantslib : 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 plantslib : grow_tree ( pos , nodes_or_function_or_model )
minetest.spawn_tree ( pos , nodes_or_function_or_model )
end
-- Check for infinite stacks
2020-03-30 22:43:46 +02:00
if minetest.get_modpath ( " unified_inventory " ) or not minetest.settings : get_bool ( " creative_mode " ) then
2020-03-30 22:41:58 +02:00
plantslib.expect_infinite_stacks = false
else
plantslib.expect_infinite_stacks = true
end
-- read a field from a node's definition
function plantslib : get_nodedef_field ( nodename , fieldname )
if not minetest.registered_nodes [ nodename ] then
return nil
end
return minetest.registered_nodes [ nodename ] [ fieldname ]
end
minetest.after ( 0 , function ( )
2020-03-30 23:09:30 +02:00
minetest.log ( " verbose " , " [Plants Lib] Registered a total of " .. ( # plantslib.surfaceslist_aircheck ) + ( # plantslib.surfaceslist_no_aircheck ) .. " surface types to be evaluated, spread " )
minetest.log ( " verbose " , " [Plants Lib] across " .. # plantslib.actionslist_aircheck .. " actions with air-checking and " .. # plantslib.actionslist_no_aircheck .. " actions without. " )
2020-03-30 22:41:58 +02:00
end )