-- Squaresville mapgen.lua -- Copyright Duane Robertson (duane@duanerobertson.com), 2017 -- Distributed under the LGPLv2.1 (https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) local DEBUG local abyssal_pivot = -20 local big_trees_mod = minetest.get_modpath('big_trees') --local biomemap = {} local biomes = squaresville.biomes local booty_mod = minetest.get_modpath('booty') local breaker = squaresville.breaker local checking_schem_matches local civ_base = squaresville.civ_base local cloudcity_mod -- = minetest.get_modpath('cloudcity') local dangerous = squaresville.dangerous_terrain local data = {} -- This is not thread-safe. local defmap = {} local desolation = squaresville.desolation local fun_caves_mod = minetest.get_modpath('fun_caves') local geomoria_mod = minetest.get_modpath('geomoria') local get_decoration = squaresville.get_decoration --local heightmap = {} local heightmap_p local lake_depth = squaresville.lake_depth / 200 local math_abs = math.abs local math_floor = math.floor local math_max = math.max local math_min = math.min local max_height = 31000 local no_buildings = squaresville.no_buildings local node = squaresville.node local noore_mod = minetest.get_modpath('noore') local os_clock = os.clock local overgen = 0 local p2data = {} -- vm rotation data buffer local passages_mod = minetest.get_modpath('passages') local river_base = squaresville.river_base local river_cutoff = squaresville.river_cutoff local schem_arrays = {} local string_match = string.match local terrain_scale_2 = squaresville.terrain_scale_2 local terrain_scale = squaresville.terrain_scale local water_base = squaresville.water_level_base_mod local zigg_mod = minetest.get_modpath('zigg') -- These seem to help the y loop some (but not much). local node_air = node['air'] local node_concrete = node['squaresville:concrete'] local node_road = node['squaresville:road'] local node_road_yellow_line = node['squaresville:road_yellow_line'] local node_sidewalk = node['squaresville:sidewalk'] local node_streetlight = node['squaresville:streetlight'] local node_stone = node['default:stone'] squaresville.fix_lighting = false squaresville.chunks = 0 -- This will FAIL if run on multiple threads. if squaresville.single_node or squaresville.single_node_ruin then squaresville.real_get_mapgen_object = minetest.get_mapgen_object minetest.get_mapgen_object = function(object) if object == 'heightmap' then return table.copy(heightmap_p) else return squaresville.real_get_mapgen_object(object) end end end -- Return information on proximity to roads and cities. local function _get_zone(pos, dist_z, def) if not def then def = {} end def.attenuation = 1 def.biome = nil def.biome_height = nil def.city = true def.civ = false def.danger = math_floor(math_max(math_abs(pos.x) / 6000, math_abs(pos.z) / 6000)) def.ground_1 = nil def.ground_2 = nil def.heat = nil def.heat_1 = nil def.heat_2 = nil def.height = nil def.humidity = nil def.humidity_1 = nil def.humidity_2 = nil def.inter_city_road = false def.pre_river_height = nil def.river = nil def.road_center_here = false def.road_center_orient = 0 def.road_here = false def.sewer = false def.sewer_wall = false def.sidewalk_here = false def.streetlight_here = false def.suburb = true def.water_height = water_base if not dist_z then local center_z = math_floor((pos.z + squaresville.center_add) / squaresville.wild_limits) * squaresville.wild_limits dist_z = math_abs(center_z - (pos.z + squaresville.max_height)) end local center_x = math_floor((pos.x + squaresville.center_add) / squaresville.wild_limits) * squaresville.wild_limits local dist_x = math_abs(center_x - (pos.x + squaresville.max_height)) local dist1 = math_max(dist_x, dist_z) if dist1 >= squaresville.suburb_limits + squaresville.half_road_size + 1 then -- in the wild def.city = false def.suburb = false elseif dist1 < squaresville.city_limits - squaresville.half_road_size then -- in the city def.suburb = false def.civ = true else -- in the suburbs def.city = false def.civ = true end local road_x = (dist_x + squaresville.half_road_size) % squaresville.block_plus_road_size local road_z = (dist_z + squaresville.half_road_size) % squaresville.block_plus_road_size if def.civ and not no_buildings then if road_x < squaresville.road_size or road_z < squaresville.road_size then def.road_here = true if not (road_x < squaresville.road_size and road_z < squaresville.road_size) and not (road_x ~= squaresville.half_road_size and road_z ~= squaresville.half_road_size) then def.road_center_here = true if road_z == squaresville.half_road_size then def.road_center_orient = 1 end end if road_x >= squaresville.half_road_size - 1 and road_x <= squaresville.half_road_size + 1 and road_z >= squaresville.half_road_size - 1 and road_z <= squaresville.half_road_size + 1 then if road_x == squaresville.half_road_size and road_z == squaresville.half_road_size then def.sewer = true else def.sewer_wall = true end end end end local side_size_x = (dist_x + squaresville.half_road_size + 2) % squaresville.block_plus_road_size local side_size_z = (dist_z + squaresville.half_road_size + 2) % squaresville.block_plus_road_size if def.civ and (not def.road_here) and (side_size_x < squaresville.road_size + 4 or side_size_z < squaresville.road_size + 4) then def.sidewalk_here = true local light_x = (pos.x + squaresville.max_height + squaresville.half_road_size + 2) % squaresville.block_plus_road_size local light_z = (pos.z + squaresville.max_height + squaresville.half_road_size + 2) % squaresville.block_plus_road_size if light_x == squaresville.road_size + 2 and light_z % 20 == 5 then def.streetlight_here = 1 elseif light_z == squaresville.road_size + 2 and light_x % 20 == 15 then def.streetlight_here = 0 elseif light_x == 1 and light_z % 20 == 15 then def.streetlight_here = 3 elseif light_z == 1 and light_x % 20 == 5 then def.streetlight_here = 2 end end local road_dist = math_min(dist_x - squaresville.half_road_size, dist_z - squaresville.half_road_size) if road_dist == - squaresville.half_road_size then def.road_center_here = true if dist_z - squaresville.half_road_size < dist_x - squaresville.half_road_size then def.road_center_orient = 1 end end road_dist = math_max(0, road_dist) if road_dist == 0 and not no_buildings then def.road_here = true def.inter_city_road = true end -- Attenuation is used to slope the terrain near towns. dist1 = dist1 - squaresville.suburb_limits - squaresville.half_road_size - 1 if dist1 < squaresville.attenuation then def.attenuation = dist1 / squaresville.attenuation end if road_dist < squaresville.attenuation then def.attenuation = math_min(def.attenuation, road_dist / squaresville.attenuation) end return def end function squaresville.get_zone(pos) return _get_zone(pos) end -- Altitude has to be calculated for spawns (for a single point) -- as well as for terrain (from tables of values), and doing it -- in two locations is error-prone, so it all stays here. local function _get_altitude_from_def(def) def.height = def.ground_1 def.river = def.river - def.ground_2 / terrain_scale_2 local g2 = def.ground_2 if def.civ then def.height = civ_base if not (def.road_here or def.sidewalk_here) then -- Slightly alter flat terrain. if def.ground_1 > squaresville.half_terrain_scale then def.height = def.height + 1 elseif def.ground_1 < squaresville.half_terrain_scale_neg then def.height = def.height - 1 end end elseif def.road_here then def.height = civ_base else if math_abs(def.height) < 10 then g2 = g2 * math_abs(def.height) * 0.1 end -- Add mountains and islands. if def.height > 0 then if g2 < 0 then g2 = -g2 * g2 * lake_depth end else if g2 < abyssal_pivot then g2 = 2 * (abyssal_pivot - g2) + abyssal_pivot end end def.height = def.height + g2 + water_base -- Slope the terrain at the edges of town to let it blend better. def.height = math_floor((def.height - civ_base) * def.attenuation + civ_base) end return def end local function _rivers_from_def(def) -- Lower the rivers in dry regions. if def.humidity_1 < 50 then def.river = def.river - (def.humidity_1 / 10) + 5 end def.pre_river_height = def.height local t_base = river_base if def.civ then t_base = civ_base elseif def.attenuation < 1 then t_base = math_floor(river_base * def.attenuation + civ_base * (1 - def.attenuation)) end if def.height > abyssal_pivot and def.river < river_cutoff then if def.civ then def.height = civ_base end def.water_height = math_min(t_base, def.height) - 3 local bottom = math_min(t_base, def.height) + math_floor((def.river - river_cutoff) * 2) - water_base + river_base def.height = bottom elseif def.civ then -- No river valley effect necessary, since urban areas are flat. elseif def.height > t_base and def.river < squaresville.river_zone then def.height = math_max(t_base, math_floor((def.height - t_base) * math_max(0, def.river - river_cutoff) / squaresville.river_scale_less_one) + t_base) end -- River biomes use the height of the surrounding terrain. def.biome_height = def.height return def end function squaresville.get_altitude(pos) local noise = squaresville.noise local pos_2d = {x=pos.x, y=pos.z} local def = squaresville.get_zone(pos) def.ground_1 = minetest.get_perlin(noise['ground_1'].def):get2d(pos_2d) def.ground_2 = minetest.get_perlin(noise['ground_2'].def):get2d(pos_2d) def.river = math_abs(minetest.get_perlin(noise['river'].def):get2d(pos_2d)) def.heat_1 = minetest.get_perlin(noise['heat_1'].def):get2d(pos_2d) def.heat_2 = minetest.get_perlin(noise['heat_2'].def):get2d(pos_2d) def.humidity_1 = minetest.get_perlin(noise['humidity_1'].def):get2d(pos_2d) def.humidity_2 = minetest.get_perlin(noise['humidity_2'].def):get2d(pos_2d) def = _get_altitude_from_def(def) return _rivers_from_def(def) end local function get_biome_from_def(def) if not def then return end -- Adjust height for standard biomes. def.biome_height = def.biome_height - water_base + 1 if def.biome_height >= water_base and def.pre_river_height > def.biome_height then def.biome_height = math_max(def.biome_height, 8) end local height_factor = 0 if def.biome_height > 20 then local h2 = def.biome_height - 20 height_factor = - h2 * h2 / (terrain_scale + terrain_scale_2) end def.heat, def.humidity = def.heat_1, def.humidity_1 if dangerous and def.danger < 1 then def.heat = math_min(math_max(def.heat, 40), 80) def.humidity = math_min(math_max(def.humidity, 40), 80) end if def.civ and desolation == 0 then def.humidity = squaresville.suburb_humidity end def.heat = def.heat + def.heat_2 + height_factor def.humidity = def.humidity + def.humidity_2 local biome_name, biome_diff for name, biome in pairs(biomes) do if not dangerous or def.danger >= (biome.danger or 0) then if (biome.y_min or -31000) <= def.biome_height and (biome.y_max or 31000) >= def.biome_height then local heat_d = biome.heat_point - def.heat local humidity_d = biome.humidity_point - def.humidity local danger_d = 0 if dangerous then danger_d = (biome.danger or 0) - def.danger end local diff = heat_d * heat_d + humidity_d * humidity_d + danger_d * danger_d if (not biome_diff) or diff < biome_diff then biome_name, biome_diff = name, diff end end end end if def.civ and biomes[biome_name].civ then def.biome = biomes[biome_name].civ else def.biome = biome_name end return def end squaresville.get_biome_from_def = get_biome_from_def function squaresville.get_terrain(pos) local def = squaresville.get_altitude(pos) return get_biome_from_def(def) end -- This is a simple, breadth-first search, for checking whether -- a depression in the ground is enclosed (for placing ponds). -- It is cpu-intensive, but also easy to code. local function heightmap_search(heightmap, csize, ri) if not heightmap[ri] then return end local s = {} local q = {} local tho = -squaresville.max_height for th = heightmap[ri], heightmap[ri] + 6 do for k, v in pairs(s) do s[k] = nil end for k, v in pairs(q) do q[k] = nil end s[ri] = true q[#q+1] = ri while #q > 0 do local ci = q[#q] local cx = (ci - 1) % csize.x local cz = math_floor((ci - 1) / csize.x) if ((cx <= 1 or cx >= csize.x - 2) or (cz <= 1 or cz >= csize.z - 2)) then break end q[#q] = nil for zo = -1, 1 do for xo = -1, 1 do if zo ~= xo then local ni = ci + (zo * csize.x) + xo if not s[ni] and heightmap[ni] <= th then s[ni] = true q[#q+1] = ni end end end end end if #q > 1 then tho = th - 1 break end end if tho >= heightmap[ri] then return tho end end local Mapgen = {} Mapgen.__index = Mapgen function Mapgen.new(minp, maxp, seed) local self = setmetatable({ area = nil, csize = nil, baseline = squaresville.baseline, biomemap = {}, -- use global? data = data, extent_bottom = squaresville.extent_bottom, extent_top = squaresville.extent_top, heightmap = {}, -- use global? meta_data = {}, minp = minp, maxp = maxp, p2data = p2data, noise = {}, schem = {}, seed = seed, terrain_point_def = {}, vm = nil, }, Mapgen) if maxp.y >= squaresville.baseline_ruin + squaresville.extent_bottom_ruin and minp.y <= squaresville.baseline_ruin + squaresville.extent_top_ruin then self.baseline = squaresville.baseline_ruin self.extent_bottom = squaresville.extent_bottom_ruin self.extent_top = squaresville.extent_top_ruin elseif maxp.y < self.baseline + self.extent_bottom or minp.y > self.baseline + self.extent_top then return nil end self.csize = vector.add(vector.subtract(maxp, minp), 1) local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") if not (vm and emin and emax) then return end self.vm = vm self.data = vm:get_data(self.data) self.p2data = vm:get_param2_data(self.p2data) self.area = VoxelArea:new({MinEdge = emin, MaxEdge = emax}) self.csize = vector.add(vector.subtract(maxp, minp), 1) return self end function Mapgen:generate(timed) -- For faux get_mapgen_object method above. heightmap_p = self.heightmap self:make_noise() local t_terrain = os_clock() self:terrain() squaresville.time_terrain = squaresville.time_terrain + os_clock() - t_terrain local occupied self:generate_caves(timed) self:generate_buildings(timed) if squaresville.single_node then for non_loop = 1, 1 do -- and (not squaresville.in_town or (geomoria_mod and geomoria and geomoria.geomoria_depth and self.maxp.y < geomoria.geomoria_depth * 80)) if self:generate_big_trees(timed) then break end if self:generate_cloud_city(timed) then break end if self:generate_moria(timed) then break end -- Past here, call things that would cover roads. if squaresville.inter_city_road then break end if self:generate_zigg(timed) then break end end end self:check_repeatability() self:save_map(timed) -- Clear any tables that won't be reused. squaresville.chunks = squaresville.chunks + 1 end function Mapgen:get_zone(pos, dist_z) return _get_zone(pos, dist_z, {}) end function Mapgen:get_altitude(index, dist_z, check) local pos = { x = (index - 1) % self.csize.x + self.minp.x, z = math_floor((index - 1) / self.csize.x) + self.minp.z } if check and (pos.x ~= check.x or pos.z ~= check.z) then print(index..': '..(pos.x-self.minp.x+1)..','..(pos.z-self.minp.z+1)..' ~= '..(check.x-self.minp.x+1)..','..(check.z-self.minp.z+1)) end local def = self:get_zone(pos, dist_z) def.ground_1 = self.noise['ground_1'].map[index] def.ground_2 = self.noise['ground_2'].map[index] def.heat_1 = self.noise['heat_1'].map[index] def.heat_2 = self.noise['heat_2'].map[index] def.humidity_1 = self.noise['humidity_1'].map[index] def.humidity_2 = self.noise['humidity_2'].map[index] def.river = math_abs(self.noise['river'].map[index]) self.terrain_point_def = def return _get_altitude_from_def(def) end function Mapgen:terrain() local area = self.area local baseline = self.baseline local biomemap = self.biomemap local biome_survey = {} local civ_base_adj = self.baseline + civ_base local csize = self.csize local data = self.data local extent_bottom = self.extent_bottom local heightmap = self.heightmap local inter_city_road local maxp = self.maxp local minp = self.minp local p2data = self.p2data local schem = self.schem local seed = self.seed local water_level_base = self.baseline + water_base local water_level_town = self.baseline - 10 squaresville.in_town = nil squaresville.suburbs = nil -- Empty the heightmap. for k, v in pairs(heightmap) do heightmap[k] = nil end for k, v in pairs(biomemap) do biomemap[k] = nil end for k, v in pairs(defmap) do defmap[k] = nil end local index = 1 for z = minp.z, maxp.z do local center_z = math_floor((z + squaresville.center_add) / squaresville.wild_limits) * squaresville.wild_limits local dist_z = math_abs(center_z - (z + squaresville.max_height)) for x = minp.x, maxp.x do local def = self:get_altitude(index, dist_z) defmap[index] = def index = index + 1 end end -- This doesn't work so well... --index = 1 --for z = minp.z, maxp.z, 8 do -- for x = minp.x, maxp.x, 8 do -- local low = squaresville.max_height -- local high = -squaresville.max_height -- local ip = 0 -- for zp = 0, 7 do -- for xp = 0, 7 do -- if defmap[index + ip].height < low then -- low = defmap[index + ip].height -- end -- if defmap[index + ip].height > high then -- high = defmap[index + ip].height -- end -- ip = ip + 1 -- end -- ip = ip + csize.x - 8 -- end -- if high - low > 5 then -- ip = 0 -- for zp = 0, 7 do -- for xp = 0, 7 do -- local d1, d2 = math_floor(low + (high - low) * 0.3), math_floor(low + (high - low) * 0.6) -- if defmap[index + ip].height <= d1 then -- defmap[index + ip].height = low -- elseif defmap[index + ip].height <= d2 then -- defmap[index + ip].height = low + 1 -- end -- ip = ip + 1 -- end -- ip = ip + csize.x - 8 -- end -- end -- index = index + 8 -- end -- index = index + csize.x * 7 --end index = 1 for z = minp.z, maxp.z do --local center_z = math_floor((z + squaresville.center_add) / squaresville.wild_limits) * squaresville.wild_limits --local dist_z = math_abs(center_z - (z + squaresville.max_height)) for x = minp.x, maxp.x do local water_level = water_level_base local def = defmap[index] def = _rivers_from_def(def) get_biome_from_def(def) if not inter_city_road and def.inter_city_road then inter_city_road = true end local civ = def.civ desolation = squaresville.get_desolation(baseline, def.danger) local height = def.height local road_center_here = def.road_center_here local road_center_orient = def.road_center_orient local road_here = def.road_here local sewer = def.sewer local sewer_wall = def.sewer_wall local sidewalk_here = def.sidewalk_here local streetlight_here = def.streetlight_here if civ and not squaresville.in_town then squaresville.in_town = true end local biome_name = def.biome biome_survey[biome_name] = (biome_survey[biome_name] or 0) + 1 biomemap[index] = biomes[biome_name] height = height + baseline heightmap[index] = height local biome_filler = node[biomes[biome_name].node_filler or 'default:stone'] local biome_riverbed = node[biomes[biome_name].node_riverbed or 'default:sand'] local biome_river_water = node[biomes[biome_name].node_river_water or 'default:water_source'] local biome_stone = node[biomes[biome_name].node_stone] or node_stone local biome_top = node[biomes[biome_name].node_top or 'default:stone'] local biome_water = node[biomes[biome_name].node_water or 'default:water_source'] local fill_1 = height - (biomes[biome_name].depth_top or 0) local fill_2 = fill_1 - (biomes[biome_name].depth_filler or 0) local water_fill_1 = water_level - (biomes[biome_name].depth_water_top or 0) local deco local ivm = area:index(x, minp.y - overgen, z) local sewer_top = civ_base_adj - 12 local sewer_bottom = civ_base_adj - 19 for y = minp.y-overgen, maxp.y+overgen do for y_non_loop = 1, 1 do if data[ivm] ~= node_air then break end if y < height and y < -100 then data[ivm] = node_stone elseif y < height then data[ivm] = biome_stone end if civ and not no_buildings then if sewer_wall and y < civ_base_adj and y >= sewer_top then data[ivm] = breaker(node_concrete, y - baseline, def.humidity_1, desolation) break elseif sewer and y == civ_base_adj + 1 then data[ivm] = breaker(node['doors:trapdoor_steel'], y - baseline, def.humidity_1, desolation) p2data[ivm] = 0 break elseif sewer and y <= civ_base_adj and y > sewer_bottom then data[ivm] = breaker(node['default:ladder_steel'], y - baseline, def.humidity_1, desolation) p2data[ivm] = 4 break elseif road_here and y > sewer_bottom and y < sewer_top then data[ivm] = node_air break elseif sidewalk_here and y > sewer_bottom and y < sewer_top then data[ivm] = breaker(node_concrete, y - baseline, def.humidity_1, desolation) break elseif road_here and (y == sewer_bottom or y == sewer_top) then data[ivm] = breaker(node_concrete, y - baseline, def.humidity_1, desolation) break end end if road_here and y == civ_base_adj then if road_center_here then data[ivm] = breaker(node_road_yellow_line, y - baseline, def.humidity_1, desolation) p2data[ivm] = road_center_orient else data[ivm] = breaker(node_road, y - baseline, def.humidity_1, desolation) end elseif sidewalk_here and y == civ_base_adj then data[ivm] = breaker(node_sidewalk, y - baseline, def.humidity_1, desolation) elseif streetlight_here and y == civ_base_adj + 1 then data[ivm] = breaker(node_streetlight, y - baseline, def.humidity_1, desolation) p2data[ivm] = streetlight_here elseif def.river < river_cutoff and y < def.water_height and y <= height and y > height - (biomes[biome_name].depth_riverbed or 0) then data[ivm] = biome_riverbed if y > def.water_height - 2 then deco = y end elseif y <= height and y > fill_1 and not road_here and not sidewalk_here then data[ivm] = biome_top deco = y elseif y <= height and y > fill_2 then data[ivm] = biome_filler elseif def.river < river_cutoff and y >= height and y <= def.water_height then data[ivm] = biome_river_water elseif y >= height and y <= water_level and y > water_fill_1 then data[ivm] = node[biomes[biome_name].node_water_top or 'default:water_source'] elseif y >= height and y <= water_level then data[ivm] = biome_water end if y <= baseline + extent_bottom then if y == baseline + extent_bottom then data[ivm] = node['squaresville:bedrock'] else data[ivm] = node_air end end end -- Interesting caves... --local h2 = noise['ground_2'].map[index] --if y < height - h2 and y > height - h2 - 10 then -- data[ivm] = node_air --end ivm = ivm + area.ystride end index = index + 1 end end squaresville.place_all_decorations(self) -- Determine the most common biome. do local bc = 0 for b, c in pairs(biome_survey) do if c > bc then bc = c squaresville.main_biome = b end end end self:generate_ponds(squaresville.main_biome) squaresville.inter_city_road = inter_city_road end function Mapgen:generate_ponds(biome) if squaresville.lake_depth < 1 then return end local area = self.area local biome_filler = squaresville.node[squaresville.biomes[biome].node_filler or 'default:stone'] local biome_water = squaresville.node[squaresville.biomes[biome].node_water or 'default:water_source'] local checked = {} local csize = self.csize local data = self.data local heightmap = self.heightmap local lilies local maxp = self.maxp local minp = self.minp local pond_min = self.baseline + 20 local ps = PcgRandom(self.seed + 93) local schem = self.schem if biome == 'deciduous_forest' or biome == 'savanna' or biome == 'rainforest' then lilies = true end -- Check for depressions every eight meters. for z = 5, csize.z - 4, 8 do for x = 5, csize.x - 4, 8 do local p = {x=x+minp.x-1, z=z+minp.z-1} local index = (z - 1) * csize.x + x local height = heightmap[index] + 1 if height > pond_min and height >= minp.y - 1 and height <= maxp.y + 1 then local search = {} local h2 = heightmap_search(heightmap, csize, index) -- If a depression is there, look at all the adjacent squares. if h2 then for zo = -8, 8 do for xo = -8, 8 do local subindex = index + zo * csize.x + xo if not checked[subindex] then h2 = heightmap_search(heightmap, csize, subindex) -- Mark this as already searched. checked[subindex] = true if h2 then -- Store depressed positions. search[#search+1] = {x=x+xo, z=z+zo, h1=heightmap[subindex], h2=h2} end end end end for _, p2 in ipairs(search) do p2.x = p2.x + minp.x - 1 p2.z = p2.z + minp.z - 1 -- Take out any trees. local rem for i, sch in ipairs(schem) do if sch.pos.x == p2.x and sch.pos.z == p2.z then rem = i break end end if rem then table.remove(schem, rem) end -- Place water and lilies. local ivm = area:index(p2.x, p2.h1-1, p2.z) data[ivm] = biome_filler for h = p2.h1, p2.h2 do ivm = ivm + area.ystride data[ivm] = biome_water end ivm = ivm + area.ystride if lilies and ps:next(1, 10) == 1 then data[ivm] = node['flowers:waterlily'] else data[ivm] = node_air end end end end end end end function Mapgen:save_map(timed) local t_over if timed then t_over = os_clock() end self.vm:set_data(self.data) self.vm:set_param2_data(self.p2data) -- Place all schematics after saving map data. for _, s in ipairs(self.schem) do squaresville.place_schematic(s.def, s.pos, self.vm, nil) end minetest.generate_ores(self.vm, self.minp, self.maxp) if booty_mod then booty.placer(self.minp, self.maxp, self.data, self.area) end if DEBUG then self.vm:set_lighting({day = 10, night = 10}) else self.vm:set_lighting({day = 0, night = 0}, self.minp, self.maxp) self.vm:calc_lighting() end self.vm:update_liquids() self.vm:write_to_map() -- Sometimes calc_lighting doesn't work without this. if squaresville.fix_lighting then minetest.after(10, function(minp, maxp) local vm = minetest.get_voxel_manip(minp, maxp) if not vm then return end vm:calc_lighting(minp, maxp) vm:update_liquids() vm:write_to_map() vm:update_map() end, emin, emax) end -- Save all meta data for chests, cabinets, etc. for _, t in ipairs(self.meta_data) do local meta = minetest.get_meta({x=t.x, y=t.y, z=t.z}) meta:from_table() meta:from_table(t.meta) end -- Call on_construct methods for nodes that request it. -- This is mainly useful for starting timers. for i, n in ipairs(self.data) do if squaresville.construct_nodes[n] then local pos = self.area:position(i) local node_name = minetest.get_name_from_content_id(n) if minetest.registered_nodes[node_name] and minetest.registered_nodes[node_name].on_construct then minetest.registered_nodes[node_name].on_construct(pos) end end end if timed then squaresville.time_overhead = squaresville.time_overhead + os_clock() - t_over end end function Mapgen:generate_moria(timed) if not geomoria_mod or not geomoria.in_range(self.minp, self.maxp) then return end local t_moria if timed then t_moria = os.clock() end local write, wetness = geomoria.geomorph(self.minp, self.maxp, self.data, self.p2data, self.area, node, self.heightmap, self.seed) local write = write or geomoria.exit_stair(self.minp, self.maxp) if timed then squaresville.time_moria = squaresville.time_moria + os_clock() - t_moria end return write end function Mapgen:generate_zigg(timed) if (not zigg_mod) or squaresville.in_town or self.minp.y > 100 or self.minp.y < -50 or (not zigg.ziggurat_biomes[squaresville.main_biome]) then return end local t_zigg if timed then t_zigg = os_clock() end local write, _ = zigg.ziggurat(self.minp, self.maxp, self.data, self.area, nil, nil, self.heightmap, self.schem) if timed then squaresville.time_zigg = squaresville.time_zigg + os_clock() - t_zigg end return write end function Mapgen:generate_cloud_city(timed) if not cloudcity_mod or self.minp.y > 129 or self.maxp.y < 47 then return end squaresville.cloudcity(self.minp, self.maxp, self.data, self.p2data, self.area, node, self.heightmap, self.seed) end function Mapgen:generate_big_trees(timed) if not big_trees_mod or tonumber(big_trees.version) and tonumber(big_trees.version) < 1.1 or self.minp.y > big_trees.tree_top or self.maxp.y < big_trees.tree_bottom then return end local t_tree, write if timed then t_tree = os.clock() end -- Try to determine if a tree will collide with a city. local h_width = math.floor(big_trees.tree_width / 2) local ex = (self.minp.x + 32) / big_trees.tree_width local ez = (self.minp.z + 32) / big_trees.tree_width ex = (ex == math.floor(ex)) ez = (ez == math.floor(ez)) local cz = (ez and self.minp.z + h_width or self.minp.z) local cx = (ex and self.minp.x + h_width or self.minp.x) local center_z = math.floor((self.minp.z + squaresville.center_add) / squaresville.wild_limits) * squaresville.wild_limits local dist_z = math.abs(center_z - (cz + squaresville.max_height)) local center_x = math.floor(((ez and self.minp.x or self.minp.x + h_width) + squaresville.center_add) / squaresville.wild_limits) * squaresville.wild_limits local dist_x = math.abs(center_x - (cx + squaresville.max_height)) local dist = math.max(dist_x, dist_z) if dist >= squaresville.suburb_limits + squaresville.half_road_size + h_width + 1 then write = big_trees.treegen(self.minp, self.maxp, self.data, self.area) end if timed then squaresville.time_trees = squaresville.time_trees + os_clock() - t_tree end return write end function Mapgen:generate_buildings(timed) if no_buildings or self.minp.y > self.baseline + 800 or self.maxp.y < self.baseline - 25 then return end local t_build if timed then t_build = os_clock() end squaresville.build(self) if timed then squaresville.time_buildings = squaresville.time_buildings + os_clock() - t_build end end function Mapgen:generate_caves(timed) local t_caves if timed then t_caves = os_clock() end if fun_caves_mod and squaresville.single_node then fun_caves.cavegen(self.minp, self.maxp, self.data, self.area, self.heightmap, self.schem) end if passages_mod and squaresville.single_node then passages.passages(self.minp, self.maxp, self.data, self.p2data, self.area, self.seed) end if fun_caves_mod and squaresville.single_node then fun_caves.decogen(self.minp, self.maxp, self.data, self.area, self.heightmap) end if noore_mod and noore.ore_in_caves > 0 and squaresville.single_node then noore.cavegen(self.minp, self.maxp, self.data, self.area) end if timed then squaresville.time_caves = squaresville.time_caves + os_clock() - t_caves end end function Mapgen:make_noise() -- Generate all squaresville noises. for n, _ in pairs(squaresville.noise) do if not squaresville.noise[n].noise then if squaresville.noise[n].is3d then squaresville.noise[n].noise = minetest.get_perlin_map(squaresville.noise[n].def, self.csize) else squaresville.noise[n].noise = minetest.get_perlin_map(squaresville.noise[n].def, {x=self.csize.x, y=self.csize.z}) end if not squaresville.noise[n].noise then return end end if not self.noise[n] then -- Keep pointers to global noise info self.noise[n] = { def = squaresville.noise[n].def, map = nil, noise = squaresville.noise[n].noise, } end -- This stays with the mapgen object. if squaresville.noise[n].is3d then self.noise[n].map = squaresville.noise[n].noise:get3dMap_flat(self.minp, self.noise[n].map) else self.noise[n].map = squaresville.noise[n].noise:get2dMap_flat({x=self.minp.x, y=self.minp.z}, self.noise[n].map) end end end function Mapgen:check_repeatability() -- This is debug code to check that everything is 100% repeatable. if not checking_schem_matches then return end table.sort(self.schem, function(c,d) if c.pos.x < d.pos.x then return true elseif c.pos.x == d.pos.x and c.pos.z < d.pos.z then return true elseif c.pos.x == d.pos.x and c.pos.z == d.pos.z and c.pos.y < d.pos.y then return true else return false end end) if not schem_arrays[self.seed] then schem_arrays[self.seed] = self.schem else local wrong for i, d in pairs(self.schem) do if self.schem[i].pos then if self.schem[i].pos.x ~= schem_arrays[self.seed][i].pos.x or self.schem[i].pos.z ~= schem_arrays[self.seed][i].pos.z then wrong = i break end else print(i, dump(self.schem[i])) end end if wrong then print(self.seed, #self.schem, #schem_arrays[self.seed], wrong) elseif #self.schem > 0 then print(self.seed, 'Match!', #self.schem) end end end local function generate(minp, maxp, seed) if not (minp and maxp and seed) then return end if not squaresville.csize then squaresville.csize = vector.add(vector.subtract(maxp, minp), 1) if not squaresville.csize then return end end local mg = Mapgen.new(minp, maxp, seed) if not mg then return end mg:generate(true) end if squaresville.path then dofile(squaresville.path .. "/buildings.lua") --dofile(squaresville.path .. "/caves.lua") --dofile(squaresville.path .. "/cloudcity.lua") end local function pgenerate(...) local status, err = pcall(generate, ...) --local status, err = true --generate(...) if not status then print('Squaresville: Could not generate terrain:') print(dump(err)) collectgarbage("collect") end end if squaresville.single_node and minetest.registered_on_generateds then -- This is unsupported. I haven't been able to think of an alternative. table.insert(minetest.registered_on_generateds, 1, pgenerate) else minetest.register_on_generated(pgenerate) end function squaresville.spawnplayer(player) if minetest.get_modpath('beds') and beds and beds.spawn then local name = player:get_player_name() if beds.spawn[name] then return end end for i = 1, 200 do local x, z = math.random(4000) - 2000, math.random(4000) - 2000 --x, z = 350, -900 local pos = {x=x, z=z} pos.y = squaresville.get_altitude(pos).height if pos.y > squaresville.water_level_base_mod then pos.y = pos.y + 2 --pos = {x=1100, y=squaresville.baseline + 25, z=1000} player:setpos(pos) return true end end end