From b21c3d368077aa3a1c42ff1582cda6263c018585 Mon Sep 17 00:00:00 2001 From: Vanessa Dannenberg Date: Wed, 6 Mar 2019 17:01:02 -0500 Subject: updated cottages, areasprotector, bees, biome_lib, technic, facade, farming redo, homedecor, maptools, mesecons, moreblocks, moreores, pipeworks, quartz, travelnet, unified_inventory, unifieddyes, xban2 delete the playeranim mod, not 5.0.0 compatible. --- biome_lib/growth.lua | 80 +++++ biome_lib/init.lua | 693 ++++++++++++++++++++--------------------- biome_lib/search_functions.lua | 60 ++++ 3 files changed, 471 insertions(+), 362 deletions(-) create mode 100644 biome_lib/growth.lua create mode 100644 biome_lib/search_functions.lua (limited to 'biome_lib') diff --git a/biome_lib/growth.lua b/biome_lib/growth.lua new file mode 100644 index 0000000..56ba4ef --- /dev/null +++ b/biome_lib/growth.lua @@ -0,0 +1,80 @@ +local time_scale = ... + +-- The growing ABM + +function biome_lib:grow_plants(opts) + + local options = opts + + options.height_limit = options.height_limit or 5 + options.ground_nodes = options.ground_nodes or { "default:dirt_with_grass" } + options.grow_nodes = options.grow_nodes or { "default:dirt_with_grass" } + options.seed_diff = options.seed_diff or 0 + + local n + + if type(options.grow_plant) == "table" then + n = "multi: "..options.grow_plant[1]..", ..." + else + n = options.grow_plant + end + + options.label = options.label or "biome_lib grow_plants(): "..n + + 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, + label = options.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 p_bot = {x=pos.x, y=pos.y-1, z=pos.z} + local n_top = minetest.get_node(p_top) + local n_bot = minetest.get_node(p_bot) + local root_node = minetest.get_node({x=pos.x, y=pos.y-options.height_limit, z=pos.z}) + local walldir = nil + if options.need_wall and options.verticals_list then + walldir = biome_lib:find_adjacent_wall(p_top, options.verticals_list, options.choose_random_wall) + end + if (n_top.name == "air" or n_top.name == "default:snow") + and (not options.need_wall or (options.need_wall and walldir)) then + -- corner case for changing short junglegrass + -- to dry shrub in desert + if n_bot.name == options.dry_early_node and options.grow_plant == "junglegrass:short" then + minetest.swap_node(pos, { name = "default:dry_shrub" }) + + elseif options.grow_vertically and walldir then + if biome_lib:search_downward(pos, options.height_limit, options.ground_nodes) then + minetest.swap_node(p_top, { name = options.grow_plant, param2 = walldir}) + end + + elseif not options.grow_result and not options.grow_function then + minetest.swap_node(pos, biome_lib.air) + + else + biome_lib:replace_object(pos, options.grow_result, options.grow_function, options.facedir, options.seed_diff) + 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 diff --git a/biome_lib/init.lua b/biome_lib/init.lua index f79c1d7..e54f9a4 100644 --- a/biome_lib/init.lua +++ b/biome_lib/init.lua @@ -84,6 +84,22 @@ biome_lib.perlin_humidity = PerlinNoise(humidity_seeddiff, humidity_octaves, hum -- Local functions +local function get_biome_data(pos, perlin_fertile) + local fertility = perlin_fertile:get2d({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 + + 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 + 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 @@ -198,6 +214,87 @@ function biome_lib:register_generate_plant(biomedef, nodes_or_function_or_model) 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) @@ -208,47 +305,8 @@ function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes, local perlin_fertile_area = minetest.get_perlin(biome.seed_diff, perlin_octaves, perlin_persistence, perlin_scale) for i = 1, #snodes do - local pos = snodes[i] - local p_top = { x = pos.x, y = pos.y + 1, z = pos.z } - local noise1 = perlin_fertile_area:get2d({x=pos.x, y=pos.z}) - local noise2 = biome_lib.perlin_temperature:get2d({x=pos.x, y=pos.z}) - local noise3 = biome_lib.perlin_humidity:get2d({x=pos.x+150, y=pos.z+50}) - local biome_surfaces_string = dump(biome.surface) - local surface_ok = false - - if not biome.depth then - local dest_node = minetest.get_node(pos) - if string.find(biome_surfaces_string, dest_node.name) then - surface_ok = true - else - if string.find(biome_surfaces_string, "group:") then - for j = 1, #biome.surface do - if string.find(biome.surface[j], "^group:") - and minetest.get_item_group(dest_node.name, biome.surface[j]) then - surface_ok = true - break - end - end - end - end - elseif not string.find(biome_surfaces_string, minetest.get_node({ x = pos.x, y = pos.y-biome.depth-1, z = pos.z }).name) then - surface_ok = true - end - - if surface_ok - and 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 checkair or minetest.get_node(p_top).name == "air") - 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 + 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 @@ -257,73 +315,77 @@ function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes, 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 + 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 - 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) - 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 - 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 - if biome.spawn_replace_node then - minetest.swap_node(pos, biome_lib.air) - end + local objtype = type(nodes_or_function_or_model) - 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 + 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, 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 + minetest.swap_node(p_top, { name = nodes_or_function_or_model[math.random(#nodes_or_function_or_model)], param2 = fdir }) 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 + 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 - tries = tries + 1 + 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 @@ -333,51 +395,52 @@ end -- a surface during the initial map read stage. function biome_lib:generate_block_with_air_checking() - if #biome_lib.blocklist_aircheck > 0 then + 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] + 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. + -- 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) + local blockhash = minetest.hash_node_position(minp) - if not biome_lib.surface_nodes_aircheck.blockhash then + 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) + 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. + -- search the generated block for air-bounded surfaces the slow way. - biome_lib.surface_nodes_aircheck.blockhash = {} + 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 + 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 - biome_lib.actioncount_aircheck.blockhash = 1 + 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.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 + 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 @@ -387,66 +450,60 @@ end -- checking for air during the initial map read stage. function biome_lib:generate_block_no_aircheck() - if #biome_lib.blocklist_no_aircheck > 0 then + 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 minp = biome_lib.blocklist_no_aircheck[1][1] + local maxp = biome_lib.blocklist_no_aircheck[1][2] - local blockhash = minetest.hash_node_position(minp) + local blockhash = minetest.hash_node_position(minp) - if not biome_lib.surface_nodes_no_aircheck.blockhash then + if not biome_lib.surface_nodes_no_aircheck.blockhash then - -- directly read the block to be searched into the chunk cache + -- 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 + 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.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 + 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 --- "Record" the chunks being generated by the core mapgen - -minetest.register_on_generated(function(minp, maxp, blockseed) - biome_lib.blocklist_aircheck[#biome_lib.blocklist_aircheck + 1] = { minp, maxp } -end) - -minetest.register_on_generated(function(minp, maxp, blockseed) - biome_lib.blocklist_no_aircheck[#biome_lib.blocklist_no_aircheck + 1] = { minp, maxp } -end) - -- "Play" them back, populating them with new stuff in the process +local step_duration = tonumber(minetest.settings:get("dedicated_server_step")) minetest.register_globalstep(function(dtime) - if dtime < 0.2 -- don't attempt to populate if lag is already too high - and math.random(100) <= biome_lib.queue_run_ratio - and (#biome_lib.blocklist_aircheck > 0 or #biome_lib.blocklist_no_aircheck > 0) then - biome_lib.globalstep_start_time = minetest.get_us_time() - biome_lib.globalstep_runtime = 0 - while (#biome_lib.blocklist_aircheck > 0 or #biome_lib.blocklist_no_aircheck > 0) - and biome_lib.globalstep_runtime < 200000 do -- 0.2 seconds, in uS. - if #biome_lib.blocklist_aircheck > 0 then - biome_lib:generate_block_with_air_checking() - end - if #biome_lib.blocklist_no_aircheck > 0 then - biome_lib:generate_block_no_aircheck() - end - biome_lib.globalstep_runtime = minetest.get_us_time() - biome_lib.globalstep_start_time + 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) @@ -454,24 +511,26 @@ end) -- to prevent unpopulated map areas minetest.register_on_shutdown(function() - if #biome_lib.blocklist_aircheck > 0 then - print("[biome_lib] Stand by, playing out the rest of the aircheck mapblock log") - print("(there are "..#biome_lib.blocklist_aircheck.." entries)...") - while true do - biome_lib:generate_block_with_air_checking(0.1) - if #biome_lib.blocklist_aircheck == 0 then return end - end + 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_no_aircheck > 0 then - 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 true do - biome_lib:generate_block_no_aircheck(0.1) - if #biome_lib.blocklist_no_aircheck == 0 then return end - end + 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) @@ -519,127 +578,99 @@ function biome_lib:spawn_on_surfaces(sd,sp,sr,sc,ss,sa) local p_top = { x = pos.x, y = pos.y + 1, z = pos.z } local n_top = minetest.get_node(p_top) local perlin_fertile_area = minetest.get_perlin(biome.seed_diff, perlin_octaves, perlin_persistence, perlin_scale) - local noise1 = perlin_fertile_area:get2d({x=p_top.x, y=p_top.z}) - local noise2 = biome_lib.perlin_temperature:get2d({x=p_top.x, y=p_top.z}) - local noise3 = biome_lib.perlin_humidity:get2d({x=p_top.x+150, y=p_top.z+50}) - if noise1 > biome.plantlife_limit - and noise2 <= biome.temp_min - and noise2 >= biome.temp_max - and noise3 <= biome.humidity_min - and noise3 >= biome.humidity_max - and biome_lib:is_node_loaded(p_top) then - local n_light = minetest.get_node_light(p_top, nil) - if not (biome.avoid_nodes and biome.avoid_radius and minetest.find_node_near(p_top, biome.avoid_radius + math.random(-1.5,2), biome.avoid_nodes)) - and n_light >= biome.light_min - and n_light <= biome.light_max - and (not(biome.neighbors and biome.ncount) or #(minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, biome.neighbors)) > biome.ncount ) - and (not(biome.near_nodes and biome.near_nodes_count and biome.near_nodes_size) or #(minetest.find_nodes_in_area({x=pos.x-biome.near_nodes_size, y=pos.y-biome.near_nodes_vertical, z=pos.z-biome.near_nodes_size}, {x=pos.x+biome.near_nodes_size, y=pos.y+biome.near_nodes_vertical, z=pos.z+biome.near_nodes_size}, biome.near_nodes)) >= biome.near_nodes_count) - and (not(biome.air_count and biome.air_size) or #(minetest.find_nodes_in_area({x=p_top.x-biome.air_size, y=p_top.y, z=p_top.z-biome.air_size}, {x=p_top.x+biome.air_size, y=p_top.y, z=p_top.z+biome.air_size}, "air")) >= biome.air_count) - and pos.y >= biome.min_elevation - and pos.y <= biome.max_elevation - then - local walldir = biome_lib:find_adjacent_wall(p_top, biome.verticals_list, biome.choose_random_wall) - if biome.alt_wallnode and walldir then - if n_top.name == "air" then - minetest.swap_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.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 - end - end - end - }) -end --- The growing ABM + local fertility, temperature, humidity = get_biome_data(pos, perlin_fertile_area) -function biome_lib:grow_plants(opts) + 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) - local options = opts + if not pos_biome_ok then + return -- Outside of biome + end - 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 + 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 - local n + 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 type(options.grow_plant) == "table" then - n = "multi: "..options.grow_plant[1]..", ..." - else - n = options.grow_plant - 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 - options.label = options.label or "biome_lib grow_plants(): "..n + 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 options.grow_delay*time_scale >= 1 then - options.interval = options.grow_delay*time_scale - else - options.interval = 1 - 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 - minetest.register_abm({ - nodenames = { options.grow_plant }, - interval = options.interval, - chance = options.grow_chance, - label = options.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 p_bot = {x=pos.x, y=pos.y-1, z=pos.z} - local n_top = minetest.get_node(p_top) - local n_bot = minetest.get_node(p_bot) - local root_node = minetest.get_node({x=pos.x, y=pos.y-options.height_limit, z=pos.z}) - local walldir = nil - if options.need_wall and options.verticals_list then - walldir = biome_lib:find_adjacent_wall(p_top, options.verticals_list, options.choose_random_wall) + 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 - 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.swap_node(pos, { name = "default:dry_shrub" }) - - elseif options.grow_vertically and walldir then - if biome_lib:search_downward(pos, options.height_limit, options.ground_nodes) then - minetest.swap_node(p_top, { name = options.grow_plant, param2 = walldir}) - end - elseif not options.grow_result and not options.grow_function then - minetest.swap_node(pos, biome_lib.air) + local currentsurface = minetest.get_node(pos).name - else - biome_lib:replace_object(pos, options.grow_result, options.grow_function, options.facedir, options.seed_diff) + 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 @@ -657,15 +688,13 @@ function biome_lib:replace_object(pos, replacement, grow_function, walldir, seed return elseif growtype == "function" then local perlin_fertile_area = minetest.get_perlin(seeddiff, perlin_octaves, perlin_persistence, perlin_scale) - local noise1 = perlin_fertile_area:get2d({x=pos.x, y=pos.z}) - local noise2 = biome_lib.perlin_temperature:get2d({x=pos.x, y=pos.z}) - grow_function(pos,noise1,noise2,walldir) + 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 noise1 = perlin_fertile_area:get2d({x=pos.x, y=pos.z}) - local noise2 = biome_lib.perlin_temperature:get2d({x=pos.x, y=pos.z}) - assert(loadstring(grow_function.."(...)"))(pos,noise1,noise2,walldir) + 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}) @@ -675,71 +704,11 @@ function biome_lib:replace_object(pos, replacement, grow_function, walldir, seed end end --- function to decide if a node has a wall that's in verticals_list{} --- returns wall direction of valid node, or nil if invalid. - -function biome_lib:find_adjacent_wall(pos, verticals, randomflag) - local verts = dump(verticals) - if randomflag then - local walltab = {} - - if string.find(verts, minetest.get_node({ x=pos.x-1, y=pos.y, z=pos.z }).name) then walltab[#walltab + 1] = 3 end - if string.find(verts, minetest.get_node({ x=pos.x+1, y=pos.y, z=pos.z }).name) then walltab[#walltab + 1] = 2 end - if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z-1 }).name) then walltab[#walltab + 1] = 5 end - if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z+1 }).name) then walltab[#walltab + 1] = 4 end - - if #walltab > 0 then return walltab[math.random(1, #walltab)] end - - else - if string.find(verts, minetest.get_node({ x=pos.x-1, y=pos.y, z=pos.z }).name) then return 3 end - if string.find(verts, minetest.get_node({ x=pos.x+1, y=pos.y, z=pos.z }).name) then return 2 end - if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z-1 }).name) then return 5 end - if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z+1 }).name) then return 4 end - end - return nil -end - --- Function to search downward from the given position, looking for the first --- node that matches the ground table. Returns the new position, or nil if --- height limit is exceeded before finding it. -function biome_lib:search_downward(pos, heightlimit, ground) - for i = 0, heightlimit do - if string.find(dump(ground), minetest.get_node({x=pos.x, y=pos.y-i, z = pos.z}).name) then - return {x=pos.x, y=pos.y-i, z = pos.z} - end - end - return false -end +dofile(biome_lib.modpath .. "/search_functions.lua") +assert(loadfile(biome_lib.modpath .. "/growth.lua"))(time_scale) -function biome_lib:find_open_side(pos) - if minetest.get_node({ x=pos.x-1, y=pos.y, z=pos.z }).name == "air" then - return {newpos = { x=pos.x-1, y=pos.y, z=pos.z }, facedir = 2} - end - if minetest.get_node({ x=pos.x+1, y=pos.y, z=pos.z }).name == "air" then - return {newpos = { x=pos.x+1, y=pos.y, z=pos.z }, facedir = 3} - end - if minetest.get_node({ x=pos.x, y=pos.y, z=pos.z-1 }).name == "air" then - return {newpos = { x=pos.x, y=pos.y, z=pos.z-1 }, facedir = 4} - end - if minetest.get_node({ x=pos.x, y=pos.y, z=pos.z+1 }).name == "air" then - return {newpos = { x=pos.x, y=pos.y, z=pos.z+1 }, facedir = 5} - end - return nil -end - --- spawn_tree() on generate is routed through here so that other mods can hook --- into it. -function biome_lib:generate_tree(pos, nodes_or_function_or_model) - minetest.spawn_tree(pos, nodes_or_function_or_model) -end - --- and this one's for the call used in the growing code - -function biome_lib:grow_tree(pos, nodes_or_function_or_model) - minetest.spawn_tree(pos, nodes_or_function_or_model) -end -- Check for infinite stacks diff --git a/biome_lib/search_functions.lua b/biome_lib/search_functions.lua new file mode 100644 index 0000000..d665b5f --- /dev/null +++ b/biome_lib/search_functions.lua @@ -0,0 +1,60 @@ + +-- function to decide if a node has a wall that's in verticals_list{} +-- returns wall direction of valid node, or nil if invalid. + +function biome_lib:find_adjacent_wall(pos, verticals, randomflag) + local verts = dump(verticals) + if randomflag then + local walltab = {} + + if string.find(verts, minetest.get_node({ x=pos.x-1, y=pos.y, z=pos.z }).name) then walltab[#walltab + 1] = 3 end + if string.find(verts, minetest.get_node({ x=pos.x+1, y=pos.y, z=pos.z }).name) then walltab[#walltab + 1] = 2 end + if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z-1 }).name) then walltab[#walltab + 1] = 5 end + if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z+1 }).name) then walltab[#walltab + 1] = 4 end + + if #walltab > 0 then return walltab[math.random(1, #walltab)] end + + else + if string.find(verts, minetest.get_node({ x=pos.x-1, y=pos.y, z=pos.z }).name) then return 3 end + if string.find(verts, minetest.get_node({ x=pos.x+1, y=pos.y, z=pos.z }).name) then return 2 end + if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z-1 }).name) then return 5 end + if string.find(verts, minetest.get_node({ x=pos.x , y=pos.y, z=pos.z+1 }).name) then return 4 end + end + return nil +end + +-- Function to search downward from the given position, looking for the first +-- node that matches the ground table. Returns the new position, or nil if +-- height limit is exceeded before finding it. + +function biome_lib:search_downward(pos, heightlimit, ground) + for i = 0, heightlimit do + if string.find(dump(ground), minetest.get_node({x=pos.x, y=pos.y-i, z = pos.z}).name) then + return {x=pos.x, y=pos.y-i, z = pos.z} + end + end + return false +end + +function biome_lib:find_open_side(pos) + if minetest.get_node({ x=pos.x-1, y=pos.y, z=pos.z }).name == "air" then + return {newpos = { x=pos.x-1, y=pos.y, z=pos.z }, facedir = 2} + end + if minetest.get_node({ x=pos.x+1, y=pos.y, z=pos.z }).name == "air" then + return {newpos = { x=pos.x+1, y=pos.y, z=pos.z }, facedir = 3} + end + if minetest.get_node({ x=pos.x, y=pos.y, z=pos.z-1 }).name == "air" then + return {newpos = { x=pos.x, y=pos.y, z=pos.z-1 }, facedir = 4} + end + if minetest.get_node({ x=pos.x, y=pos.y, z=pos.z+1 }).name == "air" then + return {newpos = { x=pos.x, y=pos.y, z=pos.z+1 }, facedir = 5} + end + return nil +end + +-- "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) -- cgit v1.2.3