From 8015cc24a5a36b07cccf0ea6f60c62fbbc1c70dd Mon Sep 17 00:00:00 2001 From: Vanessa Ezekowitz Date: Mon, 28 Jan 2013 15:30:35 -0500 Subject: [PATCH] Added experimental settings to allow for a humidity perlin layer and to allow the spawned object to *replace* the node it would otherwise spawn on top of. Also, extended API to allow for a node to be directly named in the register_generate_plant() call. --- API.txt | 100 +++++++++++++++++++++++++++++--------------- plants_lib/init.lua | 53 +++++++++++++++++------ 2 files changed, 107 insertions(+), 46 deletions(-) diff --git a/API.txt b/API.txt index aa92d0a..2446ecc 100644 --- a/API.txt +++ b/API.txt @@ -200,6 +200,13 @@ plants_lib: spawn_on_surfaces({ -- coldness). temp_max = num, -- Maximum temperature/lower end of the Perlin -- range. Defaults to -1 (unlimited heat). + humidity_min = num, -- Minimum humidity for the plant to spawn in. + -- Like the temperature map, this is a Perlin + -- value where lower numbers mean more + -- humidity in the area. Defaults to +1 (0% + -- relative humidity). + humidity_max = num, -- Maximum humidity for the plant to spawn at. + -- Defaults to -1 (100% humidity). spawn_on_side = bool, -- Set this to true to spawn the node on one -- side of the target node rather than the -- top. The code will search for an airspace @@ -221,6 +228,10 @@ plants_lib: spawn_on_surfaces({ -- facedir function above, the values given to -- the facedir parameter are for regular -- nodes, not wallmounted. + spawn_replace_node = bool -- If set to true, the target node itself is + -- replaced by the spawned object. Overrides + -- the spawn_on_bottom and spawn_on_side + -- settings. }) By default, if a biome node, size, and count are not defined, the biome @@ -233,45 +244,49 @@ this function with two parameters: a table with the biome information, and a string or table describing what to execute if the engine finds a suitable node (see below): -plantslib:register_generate_plant(biome, function_or_treedef) +plantslib:register_generate_plant(biome, node_or_function_or_treedef) Where "biome" is a table containing about a dozen variables. biome = { - surface = "string", -- [*] what nodes to spawn on. + surface = "string", -- [*] what node to spawn on. avoid_nodes = {table}, -- [*] what nodes to avoid when spawning. avoid_radius = num, -- [*] how much distance to leave between - the object to be added and the objects - to be avoided. - rarity = num, -- how rare should this tree be in its biome? - Larger values more rare, determined by - math.random(1,100) > this + -- the object to be added and the objects + -- to be avoided. If this or the + -- avoid_nodes value is nil or omitted, + -- this check is skipped. Avoid using + -- excessively large radii. + rarity = num, -- how rare should this object be in its + -- biome? Larger values make objects more + -- rare, determined by + -- math.random(1,100) > this seed_diff = num, -- perlin seed-diff value. Defaults to 0, - which causes the function to inherit the - global value of 329. + -- which causes the function to inherit the + -- global value of 329. neighbors = {table}, -- What ground nodes must be right next to and - at the same elevation as the node to be - spawned on. Defaults to the value of the - "surface" string. + -- at the same elevation as the node to be + -- spawned on. Defaults to the value of the + -- "surface" string. ncount = num, -- at least this many of the above nodes must - be next to the node to spawn on. Any value - greater than 8 will probably cause the code - to never spawn anything. Defaults to 0. + -- be next to the node to spawn on. Any value + -- greater than 8 will probably cause the code + -- to never spawn anything. Defaults to 0. depth = num, -- how deep/thick of a layer the spawned-on - node must be. Typically used for water. - Defaults to unlimited. + -- node must be. Typically used for water. + -- Defaults to unlimited. min_elevation = num, -- minimum elevation in meters/nodes. - Defaults to -31000 (unlimited). + -- Defaults to -31000 (unlimited). max_elevation = num, -- maximum elevation. Defaults to +31000 - (unlimited). + -- (unlimited). near_nodes = {table}, -- what nodes must be in the general vicinity - of the object being spawned. + -- of the object being spawned. near_nodes_size = num, -- how wide of a search area to look for - the nodes in that list. + -- the nodes in that list. near_nodes_vertical = num, -- How high/low of an area to search from -- the target node. near_nodes_count = num, -- at least this many of those nodes must be - in the area. + -- in the area. plantlife_limit = num, -- The value compared against the generic -- "plants can grow here" Perlin noise layer. -- Smaller numbers result in more abundant @@ -279,10 +294,10 @@ biome = { -- the range of about 0 to 0.5 being most -- useful. Defaults to 0.1. temp_min = num, -- coldest allowable temperature for a plant - to spawn (that is, the highest Perlin - temperature map value). + -- to spawn (that is, the highest Perlin + -- temperature map value). temp_max = num, -- warmest allowable temperature to spawn a - plant (lowest Perlin temperature value). + -- plant (lowest Perlin temperature value). verticals_list = {table}, -- Same as with the spawn_on_surfaces -- function. } @@ -291,15 +306,21 @@ biome = { is optional. Unless explicitly stated, all unused/unsupplied parameters default to nil and will not affect the results of the code. -Regarding function_or_treedef, this must either be a string indicating the -name of the function that should be called if a suitable spawn location has -been found, OR a table with an L-Systems tree definition. +Regarding node_or_function_or_treedef, this must either be table with an +L-Systems tree definition, or a string with a node or function name. -If you specify a string with a function name, that function will be passed a -single position parameter (in the usual table format), indicating where the -object should be placed. If you specified an L-Systems tree definition, then -that definition will be passed directly to the spawn_tree() function along -with the position to spawn the tree on. +If you specified a string, the code will attempt to determine, as needed, +whether that string specifies a node name. If it does, that node will be +placed on top of the target position directly. + +If it wasn't a node, the code will assume you meant to specify a function +name, in which case that function will be passed a single position parameter +(in the usual table format), indicating where the named function should place +the object. + +If you specified a table, the code assumed this table contains an L-Systems +tree definition, then that definition will be passed directly to the +spawn_tree() function along with the position to spawn the tree on. ----- @@ -312,7 +333,6 @@ grow_plants({list of options}) These are defined like so: plants_lib:grow_plants({ - grow_plant = "string", -- Name of the node to be grown into something -- else. This value is passed to the ABM as -- the "nodenames" parameter, so it is the @@ -508,3 +528,15 @@ common temperatures on both the Centigrade and Fahrenheit scales. Note that unless you're trying to model the Moon or perhaps Mercury in your mods/maps, you probably won't need to bother with Perlin values of less than -0.56 or so. +================ +Humidity Mapping +================ + +Like the temperature map above, Perlin values can be tested to determine the +approximate humidity of the *air* in the area. This does not check for nearby +water, just general humidity. + +A value of -1 equates to 100% humidity (basically, it should be a thick fog +if it could be seen), a value of 0 is 50%, and a value of +1 is 0% (dry as a +bone). + diff --git a/plants_lib/init.lua b/plants_lib/init.lua index 55f7964..ac17249 100644 --- a/plants_lib/init.lua +++ b/plants_lib/init.lua @@ -11,7 +11,7 @@ plantslib = {} -local DEBUG = false --... except if you want to spam the console with debugging info :-) +local DEBUG = true --... except if you want to spam the console with debugging info :-) plantslib.plantlife_seed_diff = 329 -- needs to be global so other mods can see it @@ -24,7 +24,10 @@ local temperature_octaves = 3 local temperature_persistence = 0.5 local temperature_scale = 150 - +local humidity_seeddiff = 1234 +local humidity_octaves = 1 +local humidity_persistence = 0.5 +local humidity_scale = 150 -- Local functions @@ -47,14 +50,14 @@ end -- Spawn plants using the map generator -function plantslib:register_generate_plant(biomedef, funct_or_model) +function plantslib:register_generate_plant(biomedef, node_or_function_or_model) plantslib:dbg("Registered mapgen spawner:") plantslib:dbg(dump(biomedef)) - minetest.register_on_generated(plantslib:search_for_surfaces(minp, maxp, biomedef, funct_or_model)) + minetest.register_on_generated(plantslib:search_for_surfaces(minp, maxp, biomedef, node_or_function_or_model)) end -function plantslib:search_for_surfaces(minp, maxp, biomedef, funct_or_model) +function plantslib:search_for_surfaces(minp, maxp, biomedef, node_or_function_or_model) return function(minp, maxp, blockseed) local biome = biomedef @@ -71,6 +74,8 @@ function plantslib:search_for_surfaces(minp, maxp, biomedef, funct_or_model) if biome.max_count == nil then biome.max_count = 5 end if biome.plantlife_limit == nil then biome.plantlife_limit = 0.1 end if biome.near_nodes_vertical == nil then biome.near_nodes_vertical = 1 end + if biome.humidity_min == nil then biome.humidity_min = 1 end + if biome.humidity_max == nil then biome.humidity_max = -1 end plantslib:dbg("Started checking generated mapblock volume...") local searchnodes = minetest.env:find_nodes_in_area(minp, maxp, biome.surface) @@ -81,8 +86,10 @@ function plantslib:search_for_surfaces(minp, maxp, biomedef, funct_or_model) local p_top = { x = pos.x, y = pos.y + 1, z = pos.z } local perlin1 = minetest.env:get_perlin(biome.seed_diff, perlin_octaves, perlin_persistence, perlin_scale) local perlin2 = minetest.env:get_perlin(temperature_seeddiff, temperature_octaves, temperature_persistence, temperature_scale) + local perlin3 = minetest.env:get_perlin(humidity_seeddiff, humidity_octaves, humidity_persistence, humidity_scale) local noise1 = perlin1:get2d({x=p_top.x, y=p_top.z}) local noise2 = perlin2:get2d({x=p_top.x, y=p_top.z}) + local noise3 = perlin3:get2d({x=p_top.x, y=p_top.z}) if (biome.depth == nil or minetest.env:get_node({ x = pos.x, y = pos.y-biome.depth-1, z = pos.z }).name ~= biome.surface) and minetest.env:get_node(p_top).name == "air" and pos.y >= biome.min_elevation @@ -90,6 +97,8 @@ function plantslib:search_for_surfaces(minp, maxp, biomedef, funct_or_model) 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 (biome.ncount == nil or table.getn(minetest.env: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 (biome.near_nodes == nil or table.getn(minetest.env: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 @@ -109,9 +118,9 @@ function plantslib:search_for_surfaces(minp, maxp, biomedef, funct_or_model) while tries < 2 and not spawned do local pos = in_biome_nodes[math.random(1, num_in_biome_nodes)] local p_top = { x = pos.x, y = pos.y + 1, z = pos.z } - if minetest.env:find_node_near(p_top, biome.avoid_radius + math.random(-1.5,1.5), biome.avoid_nodes) == nil then + if not(biome.avoid_radius and biome.avoid_nodes) or not minetest.env:find_node_near(p_top, biome.avoid_radius + math.random(-1.5,1.5), biome.avoid_nodes) then spawned = true - if type(funct_or_model) == "table" then + if type(node_or_function_or_model) == "table" then plantslib:dbg("Spawn tree at {"..dump(pos).."}") --[[ @@ -124,10 +133,16 @@ function plantslib:search_for_surfaces(minp, maxp, biomedef, funct_or_model) end ]]-- - minetest.env:spawn_tree(pos, funct_or_model) - else - plantslib:dbg("Call function: "..funct_or_model.."("..dump(pos)..")") - assert(loadstring(funct_or_model.."("..dump(pos)..")"))() + minetest.env:spawn_tree(pos, node_or_function_or_model) + + elseif type(node_or_function_or_model) == "string" then + if minetest.registered_nodes[node_or_function_or_model] == nil then + plantslib:dbg("Call function: "..node_or_function_or_model.."("..dump(pos)..")") + assert(loadstring(node_or_function_or_model.."("..dump(pos)..")"))() + else + plantslib:dbg("Add node: "..node_or_function_or_model.." at ("..dump(p_top)..")") + minetest.env:add_node(p_top, { name = node_or_function_or_model }) + end end else tries = tries + 1 @@ -168,6 +183,8 @@ function plantslib:spawn_on_surfaces(sd,sp,sr,sc,ss,sa) if biome.max_elevation == nil then biome.max_elevation = 31000 end if biome.temp_min == nil then biome.temp_min = 1 end if biome.temp_max == nil then biome.temp_max = -1 end + if biome.humidity_min == nil then biome.humidity_min = 1 end + if biome.humidity_max == nil then biome.humidity_max = -1 end if biome.plantlife_limit == nil then biome.plantlife_limit = 0.1 end if biome.near_nodes_vertical == nil then biome.near_nodes_vertical = 1 end if biome.facedir == nil then biome.facedir = 0 end @@ -188,11 +205,15 @@ function plantslib:spawn_on_surfaces(sd,sp,sr,sc,ss,sa) local n_top = minetest.env:get_node(p_top) local perlin1 = minetest.env:get_perlin(biome.seed_diff, perlin_octaves, perlin_persistence, perlin_scale) local perlin2 = minetest.env:get_perlin(temperature_seeddiff, temperature_octaves, temperature_persistence, temperature_scale) + local perlin3 = minetest.env:get_perlin(humidity_seeddiff, humidity_octaves, humidity_persistence, humidity_scale) local noise1 = perlin1:get2d({x=p_top.x, y=p_top.z}) local noise2 = perlin2:get2d({x=p_top.x, y=p_top.z}) + local noise3 = perlin3:get2d({x=p_top.x, y=p_top.z}) 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.env:get_node_light(p_top, nil) if (not(biome.avoid_nodes and biome.avoid_radius) or minetest.env:find_node_near(p_top, biome.avoid_radius + math.random(-1.5,2), biome.avoid_nodes) == nil) @@ -219,7 +240,7 @@ function plantslib:spawn_on_surfaces(sd,sp,sr,sc,ss,sa) local plant_to_spawn = biome.spawn_plants[rnd] plantslib:dbg("Chose entry number "..rnd.." of "..biome.spawn_plants_count) - if not biome.spawn_on_side and not biome.spawn_on_bottom then + if not biome.spawn_on_side and not biome.spawn_on_bottom and not biome.spawn_replace_node then local fdir = biome.facedir if biome.random_facedir then fdir = math.random(biome.random_facedir[1],biome.random_facedir[2]) @@ -229,6 +250,14 @@ function plantslib:spawn_on_surfaces(sd,sp,sr,sc,ss,sa) plantslib:dbg("Spawn: "..plant_to_spawn.." on top of ("..dump(pos)..")") minetest.env:add_node(p_top, { name = plant_to_spawn, param2 = fdir }) end + elseif biome.spawn_replace_node then + local fdir = biome.facedir + if biome.random_facedir then + fdir = math.random(biome.random_facedir[1],biome.random_facedir[2]) + plantslib:dbg("Gave it a random facedir: "..fdir) + end + plantslib:dbg("Spawn: "..plant_to_spawn.." to replace "..minetest.env:get_node(pos).name.." at ("..dump(pos)..")") + minetest.env:add_node(pos, { name = plant_to_spawn, param2 = fdir }) elseif biome.spawn_on_side then local onside = plantslib:find_open_side(pos) if onside then