-- https://github.com/paramat/meru/blob/master/init.lua#L52 -- Parameters must match mgv6 biome noise local np_default = { offset = 0, scale = 1, spread = {x=250, y=250, z=250}, seed = 9130, octaves = 3, persist = 0.5 } -- 2D noise for coldness local mg = snow.mapgen local scale_coldness = mg.perlin_scale local np_cold = { offset = 0, scale = 1, spread = {x=scale_coldness, y=scale_coldness, z=scale_coldness}, seed = 112, octaves = 3, persist = 0.5 } -- 2D noise for icetype local np_ice = { offset = 0, scale = 1, spread = {x=80, y=80, z=80}, seed = 322345, octaves = 3, persist = 0.5 } local function do_ws_func(a, x) local n = math.pi * x / 16000 local y = 0 for k = 1,1000 do y = y + math.sin(k^a * n)/(k^a) end return 1000*y/math.pi end -- caching functions local ws_values = {} local function get_ws_value(a, x) local v = ws_values[a] if v then v = v[x] if v then return v end else ws_values[a] = {} -- weak table, see https://www.lua.org/pil/17.1.html setmetatable(ws_values[a], {__mode = "kv"}) end v = do_ws_func(a, x) ws_values[a][x] = v return v end local plantlike_ids = {} setmetatable(plantlike_ids, {__mode = "kv"}) local function is_plantlike(id) if plantlike_ids[id] ~= nil then return plantlike_ids[id] end local node = minetest.registered_nodes[minetest.get_name_from_content_id(id)] if not node then plantlike_ids[id] = false return false end local drawtype = node.drawtype if not drawtype or drawtype ~= "plantlike" then plantlike_ids[id] = false return false end plantlike_ids[id] = true return true end local snowable_ids = {} setmetatable(snowable_ids, {__mode = "kv"}) local function is_snowable(id) if snowable_ids[id] ~= nil then return snowable_ids[id] end local node = minetest.registered_nodes[minetest.get_name_from_content_id(id)] if not node then snowable_ids[id] = false return false end local drawtype = node.drawtype if drawtype and drawtype ~= "normal" and drawtype ~= "allfaces_optional" and drawtype ~= "glasslike" then snowable_ids[id] = false return false end snowable_ids[id] = true return true end local c, replacements, mg_debug, biome_to_string local function define_contents() c = { dirt_with_grass = minetest.get_content_id("default:dirt_with_grass"), dirt = minetest.get_content_id("default:dirt"), tree = minetest.get_content_id("default:tree"), apple = minetest.get_content_id("default:apple"), snow = minetest.get_content_id("default:snow"), snow_block = minetest.get_content_id("default:snowblock"), dirt_with_snow = minetest.get_content_id("default:dirt_with_snow"), air = minetest.get_content_id("air"), ignore = minetest.get_content_id("ignore"), stone = minetest.get_content_id("default:stone"), dry_shrub = minetest.get_content_id("default:dry_shrub"), snow_shrub = minetest.get_content_id("snow:shrub_covered"), leaves = minetest.get_content_id("default:leaves"), jungleleaves = minetest.get_content_id("default:jungleleaves"), junglegrass = minetest.get_content_id("default:junglegrass"), ice = minetest.get_content_id("default:ice"), water = minetest.get_content_id("default:water_source"), papyrus = minetest.get_content_id("default:papyrus"), sand = minetest.get_content_id("default:sand"), desert_sand = minetest.get_content_id("default:desert_sand"), } replacements = snow.known_plants or {} mg_debug = snow.debug end local smooth = snow.smooth_biomes local smooth_rarity_max = mg.smooth_rarity_max or 0 local smooth_rarity_min = mg.smooth_rarity_min or 0 local smooth_rarity_dif = mg.smooth_rarity_dif or 0 local nosmooth_rarity = mg.nosmooth_rarity or 0 snow.register_on_configuring(function(name, v) if name == "debug" then mg_debug = v elseif name == "mapgen_rarity" or name == "mapgen_size" or name == "smooth_biomes" then minetest.after(0, function() smooth = snow.smooth_biomes smooth_rarity_max = mg.smooth_rarity_max smooth_rarity_min = mg.smooth_rarity_min smooth_rarity_dif = mg.smooth_rarity_dif nosmooth_rarity = mg.nosmooth_rarity local scale = mg.perlin_scale np_cold = { offset = 0, scale = 1, spread = {x=scale, y=scale, z=scale}, seed = 112, octaves = 3, persist = 0.5 } end) end end) local perlin_objs, perlins_chulen local function get_perlins(sidelen) if perlins_chulen == sidelen then return end perlins_chulen = sidelen local chulens = {x=sidelen, y=sidelen} perlin_objs = { default = minetest.get_perlin_map(np_default, chulens), cold = minetest.get_perlin_map(np_cold, chulens), ice = minetest.get_perlin_map(np_ice, chulens), } end local nbuf_default = {} local nbuf_cold = {} local nbuf_ice = {} minetest.register_on_generated(function(minp, maxp, seed) local t1 = os.clock() local x0 = minp.x local z0 = minp.z local x1 = maxp.x local z1 = maxp.z if not c then define_contents() end local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax} local data = vm:get_data() local param2s = vm:get_param2_data() local heightmap = minetest.get_mapgen_object("heightmap") local snow_tab,num = {},1 local pines_tab,pnum = {},1 get_perlins(x1 - x0 + 1) local nvals_default = perlin_objs.default:get_2dMap_flat({x=x0+150, y=z0+50}, nbuf_default) local nvals_cold, nvals_ice, ndia -- Choose biomes local pr = PseudoRandom(seed+57) -- Land biomes local biome = pr:next(1, 5) local snowy = biome == 1 -- spawns snow local alpine = biome == 3 -- rocky terrain -- Misc biome settings local icy = pr:next(1, 2) == 2 -- if enabled spawns ice in sand instead of snow blocks local shrubs = pr:next(1,2) == 1 -- spawns dry shrubs in snow local pines = pr:next(1,2) == 1 -- spawns pines -- Reseed random pr = PseudoRandom(seed+68) local nodes_added -- Loop through columns in chunk local is_smooth = smooth and not snowy local write_to_map = false local ni = 1 for z = z0, z1 do for x = x0, x1 do local in_biome = false local test if nvals_default[ni] < 0.35 then nvals_cold = nvals_cold or perlin_objs.cold:get_2dMap_flat({x=x0, y=z0}, nbuf_cold) test = math.min(nvals_cold[ni], 1) if is_smooth then if test >= smooth_rarity_max or ( test > smooth_rarity_min and pr:next(1, 1000) <= ((test-smooth_rarity_min)/smooth_rarity_dif)*1000 ) then in_biome = true end elseif test > nosmooth_rarity then in_biome = true end end if not in_biome then if alpine and test and test > (is_smooth and smooth_rarity_min or nosmooth_rarity) then -- remove trees near alpine local ground_y if data[area:index(x, maxp.y, z)] == c.air then local ytop = math.min(heightmap[ni]+20, maxp.y) local vi = area:index(x, ytop, z) for y = ytop, math.max(minp.y, heightmap[ni]-5), -1 do if data[vi] ~= c.air then ground_y = y break end vi = vi - area.ystride end end if ground_y then local vi = area:index(x, ground_y, z) for _ = minp.y - 16, ground_y do local id = data[vi] if id == c.leaves or id == c.jungleleaves or id == c.tree or id == c.apple then data[vi] = c.air nodes_added = true else break end vi = vi - area.ystride end end end else if not nvals_ice then nvals_ice = perlin_objs.ice:get_2dMap_flat({x=x0, y=z0}, nbuf_ice) nodes_added = true write_to_map = true end local icetype = nvals_ice[ni] local cool = icetype > 0 -- only spawns ice on edge of water local icebergs = icetype > -0.2 and icetype <= 0 local icehole = icetype > -0.4 and icetype <= -0.2 -- icesheet with holes local icesheet = icetype > -0.6 and icetype <= -0.4 local icecave = icetype <= -0.6 local ground_y -- avoid generating underground if data[area:index(x, maxp.y, z)] == c.air then -- search for non air node from 20 m above ground down to 5 m below ground (confined by minp and maxp) local ytop = math.min(heightmap[ni]+20, maxp.y) local vi = area:index(x, ytop, z) for y = ytop, math.max(minp.y, heightmap[ni]-5), -1 do if data[vi] ~= c.air then ground_y = y break end vi = vi - area.ystride end end if ground_y then local node = area:index(x, ground_y, z) local c_ground = data[node] if c_ground == c.dirt_with_grass then if alpine and test > nosmooth_rarity then snow_tab[num] = {ground_y, z, x, test} num = num+1 -- generate stone ground local vi = area:index(x, ground_y, z) for _ = math.max(-6, minp.y-6), ground_y do if data[vi] == c.stone then break end data[vi] = c.stone vi = vi - area.ystride end elseif pines and pr:next(1,36) == 1 then pines_tab[pnum] = {x=x, y=ground_y+1, z=z} pnum = pnum+1 elseif shrubs and pr:next(1,928) == 1 then data[node] = c.dirt_with_snow data[area:index(x, ground_y+1, z)] = c.dry_shrub else if snowy or test > (is_smooth and smooth_rarity_max or nosmooth_rarity) then -- more, deeper snow data[node] = c.snow_block else data[node] = c.dirt_with_snow end snow_tab[num] = {ground_y, z, x, test} num = num+1 end elseif c_ground == c.water then if not icesheet and not icecave and not icehole then local y = data[node - area.ystride] local ice = y ~= c.water and y ~= c.ice if not ice then ndia = ndia or { area.zstride - 1, 1, -2*area.zstride - 2, area.zstride, 1 - area.zstride, 0 } local vi = node + 1 for n = 1,6 do local i = data[vi] if i ~= c.water and i ~= c.ice and i ~= c.air and i ~= c.ignore then ice = true break end vi = vi + ndia[n] end if not ice and (cool or icebergs) and pr:next(1,4) == 1 then local vi_ice = node + 1 for i = 1,6 do if data[vi_ice] == c.ice then ice = true break end vi_ice = vi_ice + ndia[i] end end end if ice or (icebergs and pr:next(1,6) == 1) then data[node] = c.ice end else if icesheet or icecave or (icehole and pr:next(1,10) > 1) then data[node] = c.ice end if icecave then local vi = area:index(x, ground_y-1, z) for _ = math.max(minp.y-16, -33), ground_y-1 do if data[vi] ~= c.water then break end data[vi] = c.air vi = vi - area.ystride end end if icesheet then -- put snow onto icesheets snow_tab[num] = {ground_y, z, x, test} num = num+1 end end elseif c_ground == c.sand then if icy then data[node] = c.ice end snow_tab[num] = {ground_y, z, x, test} num = num+1 elseif c_ground == c.papyrus then snow_tab[num] = {ground_y, z, x, test} num = num+1 -- replace papyrus plants with snowblocks local vi = area:index(x, ground_y, z) for _ = 1,7 do if data[vi] ~= c.papyrus then break end data[vi] = c.snow_block vi = vi - area.ystride end elseif alpine then -- make stone pillars out of trees and other stuff local vi = area:index(x, ground_y, z) for _ = 0, ground_y - math.max(-6, minp.y-6) do if data[vi] == c.stone then break end data[vi] = c.stone vi = vi - area.ystride end -- put snow onto it snow_tab[num] = {ground_y, z, x, test} num = num+1 elseif c_ground ~= c.desert_sand then if is_snowable(c_ground) then -- put snow onto it snow_tab[num] = {ground_y, z, x, test} num = num+1 end local vi = area:index(x, ground_y, z) for _ = 0, 12 do local nd = data[vi] local plantlike = is_plantlike(nd) if replacements[nd] then data[vi] = replacements[nd] if plantlike then param2s[vi] = pr:next(0,179) end elseif nd == c.dirt_with_grass then data[vi] = c.dirt_with_snow break elseif plantlike then local under = vi - area.ystride if data[under] == c.dirt_with_grass then -- replace other plants with shrubs data[vi] = c.snow_shrub param2s[vi] = pr:next(0,179) data[under] = c.dirt_with_snow break end elseif nd == c.stone then break end vi = vi - area.ystride end end end end ni = ni + 1 end end -- abort if mapgen doesn't change sth if not nodes_added then return end if num ~= 1 then for i = 1, num-1 do i = snow_tab[i] -- set snow data[area:index(i[3], i[1]+1, i[2])] = c.snow end for k = 1, num-1 do local i = snow_tab[k] local y,z,x,test = unpack(i) test = (test-nosmooth_rarity)/(1-nosmooth_rarity) -- /(1-0.53) if test > 0 then local maxh = math.floor(test*10)%10+1 if maxh ~= 1 then local h = math.floor(get_ws_value(2, x) + get_ws_value(5, z)*5)%10+1 if h ~= 1 then -- search for nearby snow y = y+1 for off = -1,1,2 do for _,cord in pairs({{x+off,z}, {x,z+off}}) do local nd = data[area:index(cord[1], y, cord[2])] if nd == c.air or is_plantlike(nd) then h = h/2 end end end h = math.floor(h+0.5) if h > 1 then -- make snowdrifts walkable if h == 10 then h = 5 end h = math.min(maxh, h) local vi = area:index(x, y, z) if h == 9 then -- replace the snow with a snowblock because its a full node data[vi] = c.snow_block else -- set a specific snow height param2s[vi] = h*7 end end end end end end end -- spawn pines if pines and pnum ~= 1 then local spawn_pine = snow.voxelmanip_pine for i = 1, pnum-1 do spawn_pine(pines_tab[i], area, data) end end vm:set_data(data) vm:set_param2_data(param2s) vm:set_lighting({day=0, night=0}) vm:calc_lighting() vm:write_to_map() if write_to_map and mg_debug then -- print if any column of mapchunk was snow biome local biome_string = biome_to_string(biome) local chugent = math.ceil((os.clock() - t1) * 1000) print("[snow] "..biome_string.." x "..minp.x.." z "..minp.z.." time "..chugent.." ms") end end) -- Debugging function local biome_strings = { {"snowy", "plain", "alpine", "normal", "normal"}, {"cool", "icebergs", "icesheet", "icecave", "icehole"} } function biome_to_string(num) local biome = biome_strings[1][num] or "unknown "..num return biome end