123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683 |
- if not minetest.global_exists("stoneworld") then stoneworld = {} end
- stoneworld.modpath = minetest.get_modpath("stoneworld")
- -- These match values in the realm-control mod.
- stoneworld.REALM_START = 5150
- stoneworld.REALM_END = 8150
- -- Content IDs used with the voxel manipulator.
- local c_air = minetest.get_content_id("air")
- local c_ignore = minetest.get_content_id("ignore")
- local c_stone = minetest.get_content_id("darkage:basaltic")
- local c_cobble = minetest.get_content_id("darkage:basaltic_rubble")
- local c_bedrock = minetest.get_content_id("bedrock:bedrock")
- local c_lava = minetest.get_content_id("lbrim:lava_source")
- local c_lava2 = minetest.get_content_id("default:lava_source")
- local c_melt = minetest.get_content_id("cavestuff:cobble_with_rockmelt")
- local c_glow = minetest.get_content_id("glowstone:cobble")
- local c_obsidian = minetest.get_content_id("cavestuff:dark_obsidian")
- local c_obsidian2 = minetest.get_content_id("default:obsidian")
- local c_worm = minetest.get_content_id("cavestuff:glow_worm")
- local c_fungus = minetest.get_content_id("cavestuff:glow_fungus")
- local c_adamant = minetest.get_content_id("default:adamant")
- -- Externally located tables for performance.
- local vm_data = {}
- local vm_light = {}
- local noisemap1 = {}
- local noisemap3 = {}
- local noisemap4 = {}
- local noisemap5 = {}
- local noisemap6 = {}
- local perlin1
- local perlin3
- local perlin4
- local perlin5
- local perlin6
- --------------------------------------------------------------------------------
- stoneworld.caveseed = PseudoRandom(357)
- stoneworld.caveroutenoise = {
- offset = 0,
- scale = 1,
- spread = {x=75, y=75, z=75},
- seed = 0,
- octaves = 10,
- persist = 0.5,
- lacunarity = 1.6,
- }
- stoneworld.caveheightnoise = {
- offset = 0,
- scale = 1,
- spread = {x=256, y=256, z=256},
- seed = 0,
- octaves = 4,
- persist = 0.5,
- lacunarity = 2.0,
- }
- stoneworld.caves = stoneworld.caves or {}
- if not stoneworld.registered then
- -- Construct a few cave levels every 50 meters for 3000 meters height.
- -- Multiple overlapping cave networks ensures a probability of intersection.
- for k = 1, 60 do
- -- Cave network 1.
- stoneworld.caves[#stoneworld.caves + 1] = {}
- stoneworld.caves[#stoneworld.caves].y_level = k * 50 - 10
- -- Cave network 2.
- stoneworld.caves[#stoneworld.caves + 1] = {}
- stoneworld.caves[#stoneworld.caves].y_level = k * 50
- -- Cave network 3.
- stoneworld.caves[#stoneworld.caves + 1] = {}
- stoneworld.caves[#stoneworld.caves].y_level = k * 50 + 10
- end
- for k, v in ipairs(stoneworld.caves) do
- v.noise_route = table.copy(stoneworld.caveroutenoise)
- v.noise_route.seed = stoneworld.caveseed:next()
- v.route_map = {} -- Storage for bulk noise data.
- v.noise_height = table.copy(stoneworld.caveheightnoise)
- v.noise_height.seed = stoneworld.caveseed:next()
- v.height_map = {} -- Storage for bulk noise data.
- end
- end
- --------------------------------------------------------------------------------
- stoneworld.cavernseed = PseudoRandom(548)
- stoneworld.cavernceilingnoise = {
- offset = 0,
- scale = 1,
- spread = {x=64, y=64, z=64},
- seed = 0,
- octaves = 7,
- persist = 0.5,
- lacunarity = 1.6,
- }
- stoneworld.cavernfloornoise = {
- offset = 0,
- scale = 1,
- spread = {x=64, y=64, z=64},
- seed = 0,
- octaves = 7,
- persist = 0.5,
- lacunarity = 1.6,
- }
- stoneworld.cavernlevelnoise = {
- offset = 0,
- scale = 1,
- spread = {x=512, y=512, z=512},
- seed = 0,
- octaves = 3,
- persist = 0.5,
- lacunarity = 2.0,
- }
- stoneworld.caverns = stoneworld.caverns or {}
- if not stoneworld.registered then
- -- Construct a cavern level every 500 meters for 3000 meters height.
- for k = 1, 5 do
- stoneworld.caverns[#stoneworld.caverns + 1] = {}
- stoneworld.caverns[#stoneworld.caverns].y_level = k * 500
- end
- for k, v in ipairs(stoneworld.caverns) do
- v.noise_ceiling = table.copy(stoneworld.cavernceilingnoise)
- v.noise_ceiling.seed = stoneworld.cavernseed:next()
- v.ceiling_map = {} -- Storage for bulk noise data.
- v.noise_floor = table.copy(stoneworld.cavernfloornoise)
- v.noise_floor.seed = stoneworld.cavernseed:next()
- v.floor_map = {} -- Storage for bulk noise data.
- v.noise_level = table.copy(stoneworld.cavernlevelnoise)
- v.noise_level.seed = stoneworld.cavernseed:next()
- v.level_map = {} -- Storage for bulk noise data.
- end
- end
- --------------------------------------------------------------------------------
- -- Bedrock layer thickness variation.
- stoneworld.noise1param2d = {
- offset = 0,
- scale = 1,
- spread = {x=64, y=64, z=64},
- seed = 2721,
- octaves = 4,
- persist = 0.7,
- lacunarity = 2.1,
- }
- stoneworld.noise3param2d = {
- offset = 0,
- scale = 1,
- spread = {x=64, y=64, z=64},
- seed = 1248,
- octaves = 4,
- persist = 0.7,
- lacunarity = 1.5,
- }
- stoneworld.noise4param3d = {
- offset = 0,
- scale = 1,
- spread = {x=64, y=64, z=64},
- seed = 1762,
- octaves = 5,
- persist = 0.7,
- lacunarity = 1.5,
- }
- -- Used to limit where lava fortresses may spawn.
- stoneworld.noise5param3d = {
- offset = 0,
- scale = 1,
- spread = {x=1024, y=128, z=1024},
- seed = 7218,
- octaves = 4,
- persist = 0.5,
- lacunarity = 2.0,
- }
- -- Adamantine spires.
- stoneworld.noise6param2d = {
- -- Applying offset makes the spires rare, without messing with their size,
- -- as adjusting 'spread' would do.
- offset = -0.200,
- scale = 1,
- spread = {x=128, y=128, z=128},
- seed = 17355,
- octaves = 7,
- persist = 0.5,
- lacunarity = 1.9,
- }
- --------------------------------------------------------------------------------
- stoneworld.generate_realm = function(minp, maxp, seed)
- local nbeg = stoneworld.REALM_START
- local nend = stoneworld.REALM_END
- -- Don't run for out-of-bounds mapchunks.
- if minp.y > nend or maxp.y < nbeg then
- return
- end
- -- Grab the voxel manipulator.
- -- Read current map data.
- local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
- vm:get_data(vm_data)
- vm:get_light_data(vm_light)
- -- Actual emerged area should be bigger than the chunk we're generating!
- assert(emin.y < minp.y and emin.x < minp.x and emin.z < minp.z)
- assert(emax.y > maxp.y and emax.x > maxp.x and emax.z > maxp.z)
- -- Start out by overgenerating by 1 node.
- local x1 = maxp.x + 1
- local y1 = maxp.y + 1
- local z1 = maxp.z + 1
- local x0 = minp.x - 1
- local y0 = minp.y - 1
- local z0 = minp.z - 1
- local max_area = VoxelArea:new {MinEdge=emin, MaxEdge=emax}
- local min_area = VoxelArea:new {MinEdge={x=x0, y=y0, z=z0}, MaxEdge={x=x1, y=y1, z=z1}}
- -- Compute side lengths.
- local side_len_x = ((x1-x0)+1)
- local side_len_y = ((y1-y0)+1)
- local side_len_z = ((z1-z0)+1)
- local sides2D = {x=side_len_x, y=side_len_z}
- local sides3D = {x=side_len_x, y=side_len_z, z=side_len_y}
- local bp2d = {x=x0, y=z0}
- local bp3d = {x=x0, y=y0, z=z0}
- local pr = PseudoRandom(seed + 7114)
- ------------------------------------------------------------------------------
- -- Initialize cave perlin noise objects.
- for k, v in ipairs(stoneworld.caves) do
- v.perlin_route = v.perlin_route or minetest.get_perlin_map(v.noise_route, sides2D)
- v.perlin_height = v.perlin_height or minetest.get_perlin_map(v.noise_height, sides2D)
- end
- -- Calculate bulk perlin noise for caves.
- for k, v in ipairs(stoneworld.caves) do
- -- Optimization: each cave network only goes up or down not more than 70
- -- nodes, so we can skip calculating the noise for cave networks which won't
- -- get used. We do need to set a flag so we know which ones are "good".
- local good = true
- if minp.y > (nbeg + v.y_level + 70) or maxp.y < (nbeg + v.y_level - 70) then
- good = false
- end
- if good then
- v.perlin_route:get_2d_map_flat(bp2d, v.route_map)
- v.perlin_height:get_2d_map_flat(bp2d, v.height_map)
- v.good = true
- else
- v.good = false
- end
- end
- ------------------------------------------------------------------------------
- -- Initialize cavern perlin noise objects.
- for k, v in ipairs(stoneworld.caverns) do
- v.perlin_ceiling = v.perlin_ceiling or minetest.get_perlin_map(v.noise_ceiling, sides2D)
- v.perlin_floor = v.perlin_floor or minetest.get_perlin_map(v.noise_floor, sides2D)
- v.perlin_level = v.perlin_level or minetest.get_perlin_map(v.noise_level, sides2D)
- end
- -- Calculate bulk perlin noise for caverns
- for k, v in ipairs(stoneworld.caverns) do
- -- Optimization: each cavern network only goes up or down not more than 150
- -- nodes, so we can skip calculating the noise for cave networks which won't
- -- get used. We do need to set a flag so we know which ones are "good".
- local good = true
- if minp.y > (nbeg + v.y_level + 150) or maxp.y < (nbeg + v.y_level - 150) then
- good = false
- end
- if good then
- v.perlin_ceiling:get_2d_map_flat(bp2d, v.ceiling_map)
- v.perlin_floor:get_2d_map_flat(bp2d, v.floor_map)
- v.perlin_level:get_2d_map_flat(bp2d, v.level_map)
- v.good = true
- else
- v.good = false
- end
- end
- ------------------------------------------------------------------------------
- -- Get noisemaps.
- perlin1 = perlin1 or minetest.get_perlin_map(stoneworld.noise1param2d, sides2D)
- perlin1:get_2d_map_flat(bp2d, noisemap1)
- perlin3 = perlin3 or minetest.get_perlin_map(stoneworld.noise3param2d, sides2D)
- perlin3:get_2d_map_flat(bp2d, noisemap3)
- perlin4 = perlin4 or minetest.get_perlin_map(stoneworld.noise4param3d, sides3D)
- perlin4:get_3d_map_flat(bp3d, noisemap4)
- perlin5 = perlin5 or minetest.get_perlin_map(stoneworld.noise5param3d, sides3D)
- perlin5:get_3d_map_flat(bp3d, noisemap5)
- perlin6 = perlin6 or minetest.get_perlin_map(stoneworld.noise6param2d, sides2D)
- perlin6:get_2d_map_flat(bp2d, noisemap6)
- -- Localize commonly used functions for speed.
- local floor = math.floor
- local ceil = math.ceil
- local abs = math.abs
- local min = math.min
- local max = math.max
- -- Flat set if we are to spawn a fortress in this mapchunk.
- -- Note: fortress WILL extend outside the mapchunk!
- local spawn_fortress = false
- local fortress_y = 0
- -- First mapgen pass. Generate stone, passages, caverns, lava, and bedrock.
- -- Use slightly overgenerated coordinates to generate material 1 node outside
- -- the normal minp/maxp bounds. This makes it possible for the second mapgen
- -- pass (decorations) to get what is at the edge of a neighbor chunk.
- for z = z0, z1 do
- for x = x0, x1 do
- -- Get index into 2D noise arrays.
- local nx = (x - x0)
- local nz = (z - z0)
- local n2d = (side_len_z*nz+nx)
- -- Lua arrays start indexing at 1, not 0. Urrrrgh.
- n2d = n2d + 1
- local miny = y0
- local maxy = y1
- -- Clamp realm end/start Y-values.
- miny = max(miny, nbeg)
- maxy = min(maxy, nend)
- -- Get 2D noise values.
- local n1 = noisemap1[n2d]
- local n2 = noisemap3[n2d]
- local n6 = noisemap6[n2d]
- -- Pass through column.
- for y = miny, maxy do
- local vp = max_area:index(x, y, z)
- local cid = vm_data[vp]
- local nid = c_stone
- -- Don't overwrite previously existing stuff (non-ignore, non-air).
- -- This avoids ruining schematics that were previously placed, or other
- -- mapchunks.
- if (cid == c_air or cid == c_ignore) then
- -- Get index into 3D noise arrays.
- local n3d = min_area:index(x, y, z)
- local n3 = noisemap5[n3d]
- local n4 = noisemap4[n3d]
- -- Carve long winding caves.
- local caves = stoneworld.caves
- for k = 1, #caves do
- -- Skip caves which aren't part of this map chunk.
- if caves[k].good then
- -- Initial cave noise values.
- local c1 = caves[k].route_map[n2d]
- local c2 = caves[k].height_map[n2d]
- local yl = caves[k].y_level
- -- Basic cave parameters: Y-level, passage height.
- local cnoise1 = abs(c1)
- local cnoise2 = c1
- local clevel = (nbeg + yl + floor(c2 * 50)) + floor(n1 * 2)
- local cheight = 5 + abs(floor(n1 * 3))
- -- Modify cave height.
- cheight = cheight + floor(n4 * 2)
- -- Modifiers for roughening the rounding and making it less predictable.
- local z1 = abs(floor(n1))
- local z2 = abs(floor(n2))
- -- Limit determines the thickness of the cave passages.
- local limit = 0.10
- local cnoise
- local go = false
- if cnoise1 <= limit then
- cnoise = cnoise1
- go = true
- end
- if go then
- -- This bit of math is just to round off the sharp edges of the
- -- cave passages. Calculate cave top/bottom Y-values.
- local n = abs(floor((cnoise / limit) * (cheight / 2)))
- local bot = (clevel + n + z1)
- local top = (clevel + cheight - n - z2)
- if y >= bot and y <= top then
- nid = c_air
- end
- end
- end
- end
- -- Adamantine noise-level trigger.
- local spn = n6 + (n4 / 15)
- local spire_start = 1.080
- -- Carve caverns.
- local caverns = stoneworld.caverns
- for k = 1, #caverns do
- -- Skip caverns which aren't part of this map chunk.
- if caverns[k].good then
- -- Initial cavern noise values.
- local c1 = caverns[k].ceiling_map[n2d]
- local c2 = caverns[k].floor_map[n2d]
- local c3 = caverns[k].level_map[n2d]
- local yl = caverns[k].y_level
- -- Basic cavern parameters.
- local clevel = (nbeg + yl + floor(c3 * 40))
- local bot = floor(clevel - 15 + (c2 * 5) + n2 * 6)
- local top = floor(clevel + 15 + (c1 * 15) + n1 * 6)
- local lava = (nbeg + yl - 30)
- -- Pinch together floor and ceiling in the vicinity of spires.
- local shell_start = (spire_start - 0.200)
- if spn > shell_start then
- local tpinch = (spn - shell_start) * 50
- local bpinch = (spn - shell_start) * 70
- top = top - tpinch
- bot = bot + bpinch
- end
- -- If ceiling is far enough up, and bottom is below the lava
- -- ocean, then we have a chance to spawn a lava fortress here.
- if not spawn_fortress then
- if (top - bot) >= 50 and bot <= (lava - 5) then
- -- Use perlin noise to limit fortress spawning to regions.
- -- Using abs() will cause fortresses to spawn in winding strings.
- if abs(n3) < 0.1 then
- -- Only for the mapchunk intersecting the lava ocean.
- -- If we didn't do this check, it would be possible that the
- -- mapchunk ABOVE (or below) could also cause a fortress to
- -- spawn, causing a high probability of overlapping fortresses.
- if y == lava then
- spawn_fortress = true
- fortress_y = lava + 10
- end
- end
- end
- end
- -- Raise cavern ceiling over the lava ocean.
- -- Need to make room for the fortress spawner.
- local expanse_y = (lava + 15)
- if bot < expanse_y then
- top = top + (expanse_y - bot) * 2
- end
- if y >= bot and y <= top then
- nid = c_air
- end
- if y >= bot and y <= lava then
- nid = c_lava
- end
- end
- end
- -- Lava pools sometimes between rubble and basalt.
- if y <= (nbeg + 3) or y >= (nend - 3) then
- nid = c_lava
- end
- local l1 = ceil(abs(n1) * 6)
- local l2 = ceil(abs(n2) * 6)
- -- Rubble layer between bedrock and basalt.
- if y <= (nbeg + l1 + l2) or y >= (nend - l1 - l2) then
- nid = c_cobble
- end
- -- Adamantine spires.
- if spn > (spire_start + 0.070) then
- local m = floor(y + n4 * 6) % 10
- if m >= 0 and m <= 2 then
- nid = c_adamant
- elseif m == 3 then
- nid = c_lava2
- else
- nid = c_air
- end
- elseif spn > (spire_start + 0.040) then
- nid = c_adamant
- elseif spn > (spire_start + 0.020) then
- nid = c_obsidian2
- elseif spn > (spire_start + 0.000) then
- nid = c_obsidian
- end
- -- Generate bedrock floor and ceiling. Highest priority.
- if y <= (nbeg + l1) or y >= (nend - l1) then
- nid = c_bedrock
- end
- -- Write content ID.
- vm_data[vp] = nid
- end
- end -- End column loop.
- end
- end
- -- Second mapgen pass. Add context-specific modifications and decorations.
- -- No overgeneration, stay within minp/maxp bounds.
- for z = z0 + 1, z1 - 1 do
- for x = x0 + 1, x1 - 1 do
- local miny = (y0 + 1)
- local maxy = (y1 - 1)
- -- Clamp realm end/start Y-values.
- miny = max(miny, nbeg)
- maxy = min(maxy, nend)
- local worm = pr:next(1, 100) < 15
- local fungus = pr:next(1, 100) < 15
- for y = miny, maxy do
- local vd = max_area:index(x, y - 1, z)
- local vp = max_area:index(x, y, z)
- local vu = max_area:index(x, y + 1, z)
- local vn = max_area:index(x, y, z + 1)
- local vs = max_area:index(x, y, z - 1)
- local vw = max_area:index(x - 1, y, z)
- local ve = max_area:index(x + 1, y, z)
- local vne = max_area:index(x + 1, y, z + 1)
- local vnw = max_area:index(x - 1, y, z + 1)
- local vse = max_area:index(x + 1, y, z - 1)
- local vsw = max_area:index(x - 1, y, z - 1)
- -- Get what's here already.
- local cd = vm_data[vd]
- local cp = vm_data[vp]
- local cu = vm_data[vu]
- local cn = vm_data[vn]
- local cs = vm_data[vs]
- local cw = vm_data[vw]
- local ce = vm_data[ve]
- local cne = vm_data[vne]
- local cnw = vm_data[vnw]
- local cse = vm_data[vse]
- local csw = vm_data[vsw]
- local nid = cp
- -- Stone with air both above and below becomes air.
- if cp == c_stone and cu == c_air and cd == c_air then
- nid = c_air
- end
- -- Stone next to lava becomes obsidian, but requires stone below.
- if cp == c_stone and cd == c_stone and (cn == c_lava or cs == c_lava or
- cw == c_lava or ce == c_lava or cnw == c_lava or cne == c_lava or
- csw == c_lava or cse == c_lava or cu == c_lava) then
- nid = c_obsidian
- end
- -- Stone with air above and more stone below becomes rubble.
- -- But not if already turned to obsidian.
- if nid ~= c_obsidian then
- if cp == c_stone and cu == c_air and cd == c_stone then
- nid = c_cobble
- -- Sometimes place sunstone.
- if pr:next(1, 300) == 1 then
- nid = c_glow
- end
- end
- end
- -- Place glow worms on ceilings.
- if worm then
- if cp == c_air and cu == c_stone then
- nid = c_worm
- end
- end
- -- Place fungus on floors.
- if fungus then
- if cp == c_air and (cd == c_stone or cd == c_cobble) then
- nid = c_fungus
- end
- end
- -- Write content ID.
- vm_data[vp] = nid
- end
- end
- end
- -- Lighting pass. Set everything dark.
- for z = emin.z, emax.z do
- for x = emin.x, emax.x do
- for y = emin.y, emax.y do
- local vp = max_area:index(x, y, z)
- vm_light[vp] = 0
- end
- end
- end
- -- Finalize voxel manipulator.
- vm:set_data(vm_data)
- minetest.generate_ores(vm)
- vm:set_light_data(vm_light)
- vm:calc_lighting({x=emin.x, y=emin.y, z=emin.z}, {x=emax.x, y=maxp.y, z=emax.z}, true)
- vm:write_to_map()
- vm:update_liquids()
- -- A chance to spawn a fortress. Do NOT spawn a fortress in every mapchunk
- -- that's eligible, that will crowd everything else out!
- if spawn_fortress and pr:next(1, 5) == 1 then
- local p = vector.round({
- x = floor((x0 + x1) / 2),
- y = fortress_y,
- z = floor((z0 + z1) / 2),
- })
- -- Lock X,Z coords to values divisible by 11. This is the fortress step size
- -- for "default". Doing this ensures fortresses in adjacent chunks line up.
- p.x = p.x - (p.x % 11)
- p.z = p.z - (p.z % 11)
- minetest.after(0, function()
- fortress.generate(p, "default")
- end)
- end
- end
- --------------------------------------------------------------------------------
- if not stoneworld.registered then
- -- Register the mapgen callback.
- minetest.register_on_generated(function(...)
- stoneworld.generate_realm(...)
- end)
- dofile(stoneworld.modpath .. "/nodes.lua")
- dofile(stoneworld.modpath .. "/ores.lua")
- dofile(stoneworld.modpath .. "/items.lua")
- local c = "stoneworld:core"
- local f = stoneworld.modpath .. "/init.lua"
- reload.register_file(c, f, false)
- stoneworld.registered = true
- end
|