minetest-mod-squaresville/mapgen.lua

1229 lines
37 KiB
Lua

-- 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