--[[ Boulders - Should be registered before surface/terrain overrides ]] local function boulder(name,seed,nodes) minetest.register_decoration({ deco_type = "schematic", place_on = nodes, fill_ratio = 0.00000000000000001, y_min = 2, y_max = 31000, flags = "force_placement,place_center_x,place_center_z", schematic = asuna.modpath .. "/schematics/boulders/" .. name .. ".mts", rotation = "random", place_offset_y = 0 }) end local seed = 90210 for _,name in ipairs({ "cobblestone_boulder_small", "cobblestone_boulder_medium", }) do boulder(name,seed,{ "default:dirt_with_dry_grass", "default:dry_dirt_with_dry_grass", "default:dirt_with_snow", "naturalbiomes:savannalitter", }) seed = seed + 13 end for _,name in ipairs({ "mossy_cobblestone_boulder_small", "mossy_cobblestone_boulder_medium", }) do boulder(name,seed,{ "default:dirt_with_grass", "default:dirt_with_coniferous_litter", "ethereal:grove_dirt", "naturalbiomes:alpine_litter", "naturalbiomes:heath_litter", "naturalbiomes:heath_litter2", "naturalbiomes:heath_litter3", }) seed = seed + 17 end for _,name in ipairs({ "desert_boulder_small", "desert_boulder_medium", }) do boulder(name,seed,{ "default:desert_sand", "default:sandstone", }) seed = seed + 19 end --[[ Terrain changes - Sweeping terrain changes that must be made before decorations are placed ]] -- Special decoration handling local mtrd = minetest.register_decoration minetest.register_decoration = function(def) -- Set all_floors for all surface decorations local flags = def.flags or "" if not (flags:find("all_") or flags:find("liquid_")) then def.flags = #flags > 0 and (flags .. ",all_floors") or "all_floors" end -- Set negative y_min for select surface decorations if def.y_min == 1 and def.y_max and def.y_max > 1 and def.deco_type == "simple" then def.y_min = -20 end -- Ensure spawning in water for underwater decorations if def.y_max and def.y_max < 1 and not def.spawn_by and (def.place_on == "default:sand" or def.place_on[1] == "default:sand") then def.spawn_by = "default:water_source" def.num_spawn_by = 1 end -- Return original register_decoration call return mtrd(def) end -- Surface nodes that should be propagated through surface stone local surface_spread = { "default:dirt_with_grass", "default:dry_dirt_with_dry_grass", "default:dirt_with_dry_grass", "default:dirt_with_rainforest_litter", "default:dirt_with_coniferous_litter", "naturalbiomes:savannalitter", "naturalbiomes:alpine_litter", "naturalbiomes:mediterran_litter", "naturalbiomes:alderswamp_litter", "naturalbiomes:outback_litter", "ethereal:grove_dirt", "ethereal:bamboo_dirt", "livingjungle:jungleground", "livingjungle:leafyjungleground", "ethereal:mushroom_dirt", "nightshade:nightshade_dirt_with_grass", "japaneseforest:japanese_dirt_with_grass", "bambooforest:dirt_with_bamboo", "dorwinion:dorwinion_grass", "badland:badland_grass", "frost_land:frost_land_grass", "prairie:prairie_dirt_with_grass", "everness:dirt_with_crystal_grass", "everness:dirt_with_cursed_grass", "everness:dirt_with_coral_grass", "ethereal:gray_dirt", "naturalbiomes:heath_litter", "naturalbiomes:bushland_bushlandlitter", "naturalbiomes:bushland_bushlandlitter2", "naturalbiomes:bushland_bushlandlitter3", } -- Replace some surface stone with grass; also causes some biome ingress into caves for _,node in ipairs(surface_spread) do minetest.register_decoration({ deco_type = "simple", place_on = { "group:stone", "default:stone_with_coal", "default:dirt", "default:silver_sand", "default:gravel", "everness:coral_desert_stone_with_coal", "everness:cursed_stone_carved_with_coal", "everness:crystal_stone_with_coal", }, spawn_by = node, num_spawn_by = 1, sidelen = 4, y_min = 3, y_max = 31000, place_offset_y = -1, fill_ratio = 10, decoration = node, flags = "force_placement", }) end -- Ocean floor nodes that should be replaced with proper ocean floor local ocean_floor_replace = { "group:stone", "default:stone_with_coal", "everness:quartz_ore", "default:dirt", "default:dry_dirt", "default:gravel", "default:silver_sand", "default:dirt_with_grass", "default:dry_dirt_with_dry_grass", "default:dirt_with_dry_grass", "default:dirt_with_rainforest_litter", "default:dirt_with_coniferous_litter", "naturalbiomes:savannalitter", "naturalbiomes:alpine_litter", "naturalbiomes:mediterran_litter", "naturalbiomes:alderswamp_litter", "naturalbiomes:outback_litter", "ethereal:grove_dirt", "ethereal:bamboo_dirt", "livingjungle:jungleground", "livingjungle:leafyjungleground", "ethereal:mushroom_dirt", "nightshade:nightshade_dirt_with_grass", "japaneseforest:japanese_dirt_with_grass", "bambooforest:dirt_with_bamboo", "dorwinion:dorwinion_grass", "badland:badland_grass", "frost_land:frost_land_grass", "prairie:prairie_dirt_with_grass", "everness:dirt_with_crystal_grass", "everness:dirt_with_cursed_grass", "everness:coral_desert_stone_with_coal", "everness:cursed_stone_carved_with_coal", "everness:cursed_stone", "everness:crystal_stone_with_coal", } -- Ocean floor generation function local function register_ocean_floor(name) -- Get above and shore biome names local above = name:sub(1,-7) -- to trim "_below" from the end of the biome name local shore = above .. "_shore" -- Get biome local biome = asuna.biomes[name] -- Register shore ocean floor terrain minetest.register_decoration({ deco_type = "schematic", place_on = ocean_floor_replace, sidelen = 80, fill_ratio = 10, -- fill all biomes = {name,shore,above}, y_max = 0, y_min = 0, decoration = "default:stone", spawn_by = "default:water_source", num_spawn_by = 1, place_offset_y = -1, flags = "all_floors,force_placement", schematic = { size = { x = 1, y = 2, z = 1, }, data = { { name = "default:stone" , param1 = 255 , param2 = 0 }, { name = biome.seabed , param1 = 255 , param2 = 0 }, }, }, }) -- Register ocean floor terrain minetest.register_decoration({ deco_type = "schematic", place_on = ocean_floor_replace, sidelen = 80, fill_ratio = 10, -- fill all biomes = {name,shore,above}, y_max = -1, y_min = -10, decoration = "default:stone", spawn_by = "default:water_source", num_spawn_by = 1, place_offset_y = -3, flags = "all_floors,force_placement", schematic = { size = { x = 1, y = 4, z = 1, }, data = { { name = "default:stone" , param1 = 255 , param2 = 0 }, { name = biome.seabed , param1 = 255 , param2 = 0 }, { name = biome.seabed , param1 = 255 , param2 = 0 }, { name = biome.seabed , param1 = 255 , param2 = 0 }, }, }, }) -- Register deep ocean floor terrain minetest.register_decoration({ deco_type = "schematic", place_on = ocean_floor_replace, sidelen = 80, fill_ratio = 10, -- fill all biomes = {name,shore,above}, y_max = -11, y_min = -36, decoration = "default:stone", spawn_by = "default:water_source", num_spawn_by = 1, place_offset_y = -3, flags = "all_floors,force_placement", schematic = { size = { x = 1, y = 4, z = 1, }, data = { { name = "default:stone" , param1 = 255 , param2 = 0 }, { name = biome.deep_seabed , param1 = 255 , param2 = 0 }, { name = biome.deep_seabed , param1 = 255 , param2 = 0 }, { name = biome.deep_seabed , param1 = 255 , param2 = 0 }, }, }, }) -- Replace underwater surface nodes below sea level minetest.register_decoration({ deco_type = "schematic", place_on = surface_spread, spawn_by = { "default:sand", "default:water_source", }, num_spawn_by = 1, sidelen = 4, y_max = 0, y_min = -10, place_offset_y = -3, fill_ratio = 10, biomes = {name,shore,above}, schematic = { size = { x = 1, y = 4, z = 1, }, data = { { name = "default:stone" , param1 = 255 , param2 = 0 }, { name = biome.seabed , param1 = 255 , param2 = 0 }, { name = biome.seabed , param1 = 255 , param2 = 0 }, { name = biome.seabed , param1 = 255 , param2 = 0 }, }, }, flags = "all_floors,force_placement", }) end -- Generate ocean floor terrain local seed = 124 for _,biome in ipairs(asuna.biome_groups.below) do register_ocean_floor(biome) end -- Maximum pool limits local limits = { MAX_SCAN_DISTANCE = 3, MAX_DEPTH = 2, MAX_AREA = 89, } -- Relevant content IDs local cids = nil minetest.register_on_mods_loaded(function() -- load after mapgen aliases are defined cids = { lava = minetest.get_content_id("mapgen_lava_source"), water = minetest.get_content_id("mapgen_water_source"), air = minetest.CONTENT_AIR, ignore = minetest.CONTENT_IGNORE, } end) -- Array of flammable nodes to be avoided when placing lava along with invalid -- pool edge nodes local is_flammable = {} local is_invalid = { [minetest.CONTENT_AIR] = true } minetest.register_on_mods_loaded(function() for node,def in pairs(minetest.registered_nodes) do if def.groups and def.groups.flammable and def.groups.flammable > 0 then is_flammable[minetest.get_content_id(node)] = true is_invalid[minetest.get_content_id(node)] = true elseif def.floodable or def.drawtype == "airlike" or def.drawtype == "liquid" or def.drawtype == "flowingliquid" or def.buildable_to or def.walkable == false or (def.groups and def.groups.not_in_creative_inventory and def.groups.not_in_creative_inventory > 0) or node:find("_marker") -- mapgen marker names used by some mods then is_invalid[minetest.get_content_id(node)] = true end end end) -- States that liquid pool nodes can be in local nodestate = { -- Invalid states OUT_OF_BOUNDS = 128, -- outside of voxelarea or ignore nodes INVALID = 64, -- nodes that cannot be part of a pool or pool borders UNSATISFIABLE = 32, -- pool nodes that cannot be filled with liquid MASK_INVALID = 128 + 64 + 32, -- combination of invalid states -- Single nodes above the pool to be replaced with air ERASE = 16, -- surface nodes that have been flagged for erasure of nodes above MASK_ERASURE = 16 + 64, -- nodes that will be erased if the pool is generated -- Directions POSITIVE_Z = 8, POSITIVE_X = 4, NEGATIVE_Z = 2, NEGATIVE_X = 1, MASK_SATISFIED = 8 + 4 + 2 + 1, -- combination of all directions -- Blank state NONE = 0, } -- Monotonic map-like pool 'class' to track nodes and node states local function Pool() local nodemap = {} local node_iterator = {} -- for fast, ordered, deterministic iteration local size = 0 local scan_queue = {} -- for collecting non-terminated nodes to be scanned return { add = function(node,state) nodemap[node] = state table.insert(node_iterator,node) size = size + 1 return state end, put = function(node,state) nodemap[node] = state return state end, get = function(node) return nodemap[node] end, foreach = function(start,fn) for i = start, size do if not fn(node_iterator[i]) then return false end end return true end, size = function() return size end, } end -- Register advanced decoration abdecor.register_advanced_decoration("asuna_cave_pools",{ target = { sidelen = 80, fill_ratio = 0.0000925, place_on = { "group:stone", "group:soil", "default:clay", "default:stone_with_coal_ore", "default:stone_with_iron_ore", "default:sand", "default:silver_sand", "default:desert_sand", "default:gravel", "default:coalblock", "caverealms:coal_dust", "everness:moss_block", "everness:forsaken_desert_sand", "everness:crystal_moss_block", "everness:emerald_ice", "everness:ancient_emerald_ice", "everness:dense_emerald_ice", "everness:frosted_ice", "everness:frosted_ice_translucent", "caverealms:stone_with_moss", "caverealms:stone_with_lichen", "caverealms:stone_with_algae", "caverealms:stone_with_salt", "caverealms:hot_cobble", }, y_max = -36, y_min = -31000, flags = "all_floors", }, fn = function(mapgen) -- Get provided values local va = mapgen.voxelarea local vdata = mapgen.data local pos = mapgen.pos -- Get stride values and set position local ystride = va.ystride local zstride = va.zstride local pos = va:index(pos.x,pos.y,pos.z) -- Determine pool liquid based on climate -- Dry/hot climates are more likely to be lava, vice-versa with water local liquid = (function() local heatmap = minetest.get_mapgen_object("heatmap") or {} local humiditymap = minetest.get_mapgen_object("humiditymap") or {} local pos2d = mapgen.index2d(mapgen.pos) local heat = heatmap[pos2d] or 50 local humidity = humiditymap[pos2d] or 50 local climate = 50 + (heat / 2 - 25) - (humidity / 2 - 25) local pos_random = PcgRandom(mapgen.seed):next(-29,29) + climate return pos_random > 50 and cids.lava or cids.water end)() -- Create new pool for nodes local pool = Pool() -- VoxelManip offset lookup by direction local adjacent = { [nodestate.POSITIVE_X] = 1, [nodestate.POSITIVE_Z] = zstride, [nodestate.NEGATIVE_X] = -1, [nodestate.NEGATIVE_Z] = -zstride, } -- Recursive scanning function local function scan(node,direction,distance,depth) -- Get the node's state if it exists, else initialize it based on depth -- and pool size limit local state = pool.get(node) or pool.add(node,(function() if depth == 1 and pool.size() > limits.MAX_AREA then if is_invalid[vdata[node]] then return nodestate.INVALID else return nodestate.UNSATISFIABLE end else return nodestate.NONE end end)()) -- Return if this node is already in an invalid state if state > 31 then return bit.band(state,nodestate.MASK_INVALID) end -- Return if this node is already satisfied in the given direction if bit.band(state,direction) ~= 0 then return direction end -- Check if this node is out of bounds local content = vdata[node] if content == nil or content == cids.ignore then return nodestate.OUT_OF_BOUNDS end -- Check the validity of this node if is_invalid[content] then return pool.put(node,nodestate.INVALID) end -- Return unsatisfiable if the node below is invalid if is_invalid[vdata[node - ystride]] then return pool.put(node,nodestate.UNSATISFIABLE) end -- Return solution if this node is beyond the max scan distance if distance > limits.MAX_SCAN_DISTANCE then return direction end -- Checks based on depth local above = node + ystride if depth == 1 then -- Flag nodes above for erasure if the node above is solid and the node -- above that is air, but this node is unsatisfiable if both the node -- above and the node above that are solid if bit.band(state,nodestate.ERASE) == 0 and vdata[above] ~= cids.air then local above2 = above + ystride if vdata[above2] == cids.air then pool.add(above,nodestate.MASK_ERASURE) else -- Do not generate lava pools around flammable nodes that won't be -- erased if liquid == cids.lava and (is_flammable[vdata[above]] or is_flammable[vdata[above2]]) then return nodestate.OUT_OF_BOUNDS else return pool.put(node,nodestate.UNSATISFIABLE) end end state = bit.bor(state,nodestate.ERASE) -- flag this node as already having been checked for erasure end else -- At non-surface depths, the node above must have been fully satisfied -- in a previous scan if bit.band(pool.get(above) or nodestate.NONE,nodestate.MASK_SATISFIED) ~= nodestate.MASK_SATISFIED then return pool.put(node,nodestate.INVALID) end end -- Scan neighbor in the given direction local neighbor = node + adjacent[direction] local nstate = scan(neighbor,direction,distance + 1,depth) -- Return the direction if the neighbor is satisfied in the given direction if nstate == direction or nstate == nodestate.UNSATISFIABLE then pool.put(node,bit.bor(state,direction)) return direction end -- If neighbor is out of bounds, then the entire pool is invalid if nstate == nodestate.OUT_OF_BOUNDS then return nodestate.OUT_OF_BOUNDS end -- If neighbor is invalid, then this node is unsatisfiable if nstate == nodestate.INVALID then return pool.put(node,nodestate.UNSATISFIABLE) end end -- Initialize pool with the target position and perform the scan for depth = 1, limits.MAX_DEPTH do pool.add(pos - (depth - 1) * ystride,nodestate.NONE) local scan_count = 4 -- each of four -/+ x/z directions local i = 0 local scan_start = pool.size() -- optimization to skip nodes from prior scans while i < scan_count do local previous_size = pool.size() local direction = bit.lshift(1,i % 4) -- cycle through directions if not pool.foreach(scan_start,function(node) return scan(node,direction,1,depth) ~= nodestate.OUT_OF_BOUNDS -- stop scanning immediately if out of bounds end) then return -- cannot render out of bounds pools end if pool.size() > previous_size then scan_count = scan_count + 1 -- new nodes need additional scans end i = i + 1 end end -- Fill the pool and erase nodes above pool.foreach(1,function(node) local state = pool.get(node) if bit.band(state,nodestate.MASK_SATISFIED) == nodestate.MASK_SATISFIED then vdata[node] = liquid elseif state == nodestate.MASK_ERASURE then vdata[node] = cids.air end return true end) end, }) --[[ Schematics - Placed before other biome mods in order to mitigate interference from trees, etc. ]] local mpath = minetest.get_modpath("asuna_core") -- Haunted house minetest.register_decoration({ name = "asuna_core:everness_haunted_house_badland", deco_type = 'schematic', place_on = "badland:badland_grass", spawn_by = "badland:badland_grass", num_spawn_by = 8, sidelen = 80, fill_ratio = 0.0000175, biomes = "badland", y_max = 31000, y_min = 7, place_offset_y = -1, schematic = mpath .. '/schematics/everness/everness_haunted_house_badland.mts', rotation = "random", flags = 'place_center_x,place_center_z,force_placement', }) -- Jungle temple minetest.register_decoration({ name = "asuna_core:everness_jungle_temple_new", deco_type = 'schematic', place_on = "group:soil", spawn_by = "group:soil", num_spawn_by = 8, sidelen = 80, fill_ratio = 0.0000275, biomes = { "rainforest", "livingjungle:jungle", }, y_max = 31000, y_min = 8, place_offset_y = -3, schematic = mpath .. '/schematics/everness/everness_jungle_temple_new.mts', rotation = "random", flags = 'place_center_x,place_center_z,force_placement', }) -- Japanese shrine minetest.register_decoration({ name = "asuna_core:everness_japanese_shrine_new", deco_type = 'schematic', place_on = "group:soil", spawn_by = "group:soil", num_spawn_by = 7, sidelen = 80, fill_ratio = 0.0000175, biomes = { "bamboo", "japaneseforest", }, y_max = 31000, y_min = 8, place_offset_y = -1, schematic = mpath .. '/schematics/everness/everness_japanese_shrine_new.mts', rotation = "random", flags = 'place_center_x,place_center_z,force_placement', }) -- Populate chests with loot local asuna_core_everness_dids = {} for _,decoration in ipairs({ "asuna_core:everness_haunted_house_badland", "asuna_core:everness_jungle_temple_new", "asuna_core:everness_japanese_shrine_new", }) do local did = minetest.get_decoration_id(decoration) minetest.set_gen_notify({decoration = true},{did}) asuna_core_everness_dids["decoration#" .. did] = true end minetest.register_on_generated(function(minp,maxp) if maxp.y > 4 then local gennotify = minetest.get_mapgen_object("gennotify") for decoration_id,decorations in pairs(gennotify) do if asuna_core_everness_dids[decoration_id] then local chest_positions = minetest.find_nodes_in_area(minp,maxp,{ 'everness:chest' }) if #chest_positions > 0 then Everness:populate_loot_chests(chest_positions) end return end end end end)