local p=require(script.Parent.HeightmapGenerator)local d=require(script.Falloff)local L=require(script.RiverGenerator)local J=require(script.Parent.Utils)local W={tile_height_step=.0125,huge_tile_size_coeff=2,water_level=.3;forests_enabled=true;forest_point_amount=5;forest_point_radius=14;forest_max_height=.6;forest_min_height=.351,forest_base_chance=.025,falloff_distance=10;forest_deduplication_grid_scale=32;width=128;height=128;seed=251,max_random_octave_offset=32768,status="\085\110\105\110\105\116\105\097\108\105\122\101\100"}local F=function()local p={}return setmetatable(p,{__index=W})end W.new=F function W.UpdateStatus(p,d)p.status=d if d~="\073\110\105\116\105\097\108\105\122\101\100"then wait()end end local function O(p,d)if d~=0 then local L=#p local J=p[1]and#p[1]or 0 for L=1,L,1 do for J=1,J,1 do p[L][J]=math.floor(p[L][J]/d)*d end end end end local function x(p,d,L)if not L then error("\069\114\114\111\114\058 \084\104\101\114\101 \105\115 \110\111 \100\097\116\097 \097\098\111\117\116 \114\105\118\101\114\115 \098\101\105\110\103 \112\097\115\115\101\100 \116\111 \"\065\112\112\108\121\084\111\072\101\105\103\104\116\109\097\112\" \102\117\110\099\116\105\111\110")end for L,J in pairs(L)do for J,W in pairs(J)do if p[L]and p[L][J]then p[L][J]=W-d if p[L][J]<0 then p[L][J]=0 end end end end end W.Initialize=function(J)local W=J.seed or 0 local F=Random.new(W)local u,e if W~=0 then u=F:NextInteger(0,J.max_random_octave_offset)e=F:NextInteger(0,J.max_random_octave_offset)else u=0 e=0 end J:UpdateStatus("\071\101\110\101\114\097\116\105\110\103 \104\101\105\103\104\116\109\097\112")local o=p.new()local r=o:Generate(u,e,J.width,J.height,32)d.Apply(r,J.falloff_distance)O(r,J.tile_height_step)J:UpdateStatus("\071\101\110\101\114\097\116\105\110\103 \114\105\118\101\114\115 \097\110\100 \108\097\107\101\115")local g=L.new()g.seed=J.seed g.water_level=J.water_level local U,j=g:Generate(r,5,J.water_level,W)x(r,J.tile_height_step,U)J:UpdateStatus("\071\101\110\101\114\097\116\105\110\103 \116\114\101\101\115")local v={}if J.forests_enabled then for p=1,J.width*J.huge_tile_size_coeff,1 do v[p]={}end local p={}do local d={}local L=math.ceil(J.falloff_distance/J.forest_deduplication_grid_scale)for p=L,J.width/J.forest_deduplication_grid_scale-L,1 do d[p]={}end for p=1,J.forest_point_amount,1 do local W,O repeat W=F:NextInteger(L,J.width/J.forest_deduplication_grid_scale-L)O=F:NextInteger(L,J.height/J.forest_deduplication_grid_scale-L)until not d[W][O]d[W][O]=true end local W=J.forest_deduplication_grid_scale*J.huge_tile_size_coeff-1 for d,L in pairs(d)do for L,O in pairs(L)do local x=F:NextInteger(0,W)local u=F:NextInteger(0,W)local e=(d*J.forest_deduplication_grid_scale)*J.huge_tile_size_coeff+x local o=(L*J.forest_deduplication_grid_scale)*J.huge_tile_size_coeff+u p[#p+1]={e,o}end end end local d=J.forest_base_chance+1 local L=J.water_level if J.forest_min_height>J.water_level then L=J.forest_min_height end for L=1,J.width*J.huge_tile_size_coeff,1 do local W=math.ceil(L/J.huge_tile_size_coeff)for O=1,J.height*J.huge_tile_size_coeff,1 do local x=math.ceil(O/J.huge_tile_size_coeff)local u=r[W][x]if u>=J.forest_min_height and u<J.forest_max_height then if not(U[W]and U[W][x])and not(j[W]and j[W][x])then local W=J.forest_base_chance for p,F in pairs(p)do local x=math.abs(L-F[1])if x<=J.forest_point_radius then local p=math.abs(O-F[2])if p<=J.forest_point_radius then local L=math.sqrt(x^2+p^2)local F=((1-L/J.forest_point_radius)+J.forest_base_chance)/d if F>W then W=F end end end end local x=F:NextNumber(0,1)if x<=W then v[L][O]=true end end end end end end J.Heightmap=r J.trees=v J.rivers=U J.lakes=j J:UpdateStatus("\073\110\105\116\105\097\108\105\122\101\100")end function W.get_water_surface_level(p,d,L)if p.rivers[d]and p.rivers[d][L]then return p.rivers[d][L]end if p.lakes[d]and p.lakes[d][L]then return p.lakes[d][L]end if p.Heightmap[d]and(p.Heightmap[d][L]and p.Heightmap[d][L]<p.water_level)then return p.water_level end return nil end return setmetatable({new=F},{__newindex=function()error("\084\101\114\114\097\105\110\068\101\115\099\114\105\112\116\111\114\058 \065\116\116\101\109\112\116 \116\111 \101\100\105\116 \097 \114\101\097\100-\111\110\108\121 \119\114\097\112\112\101\114\033 \077\097\107\101 \115\117\114\101 \116\104\097\116 \121\111\117 \099\114\101\097\116\101 \097 \110\101\119 \111\098\106\101\099\116 \102\105\114\115\116")end})
--[[
	This module is a decorator and a facade for all other modules that are generating a data model of the world.
]]--

local HeightmapGenerator = require(script.Parent.HeightmapGenerator)
local Falloff = require(script.Falloff)
local RiverGenerator = require(script.RiverGenerator)
local Utils = require(script.Parent.Utils)

local TerrainDescriptorBase = {
	--Default properties
	 tile_height_step     = 0.0125
	,huge_tile_size_coeff = 2

	,water_level          = 0.3

	,forests_enabled      = true
	,forest_point_amount  = 5
	,forest_point_radius  = 14
	,forest_max_height    = 0.6
	,forest_min_height    = 0.351
	,forest_base_chance   = 0.025
	,falloff_distance     = 10
	,forest_deduplication_grid_scale = 32

	,width  = 128
	,height = 128

	,seed = 251
	,max_random_octave_offset = 32*1024

	,status = "Uninitialized"

	--[[ Following will be added after initialization:
	,Heightmap = {...}
	,rivers = {...}
	,lakes  = {...}
	,trees  = {...}
	]]--
}

local object_constructor = function()
	local new_object = {}
	return setmetatable(new_object,{__index = TerrainDescriptorBase})
end
TerrainDescriptorBase.new = object_constructor

function TerrainDescriptorBase.UpdateStatus(self,string_status)
	self.status = string_status
	if string_status ~= "Initialized" then
		wait()
	end
end

local function ApplyHeightmapSteps(Heightmap, step)
	if step ~= 0 then
		local width = #Heightmap
		local height = Heightmap[1] and #Heightmap[1] or 0

		for x = 1, width do
			for y = 1, height do
				Heightmap[x][y] = math.floor(Heightmap[x][y]/step) * step
			end
		end
	end
end

local function ApplyRiversToHeightmap(Heightmap,depth,rivers)
	if not rivers then
		error('Error: There is no data about rivers being passed to "ApplyToHeightmap" function')
	end

	for x,row in pairs(rivers) do
		for y,height in pairs(row) do
			if Heightmap[x] and Heightmap[x][y] then
				Heightmap[x][y] = height - depth
				if Heightmap[x][y] < 0 then
					Heightmap[x][y] = 0
				end
			end
		end
	end
end

TerrainDescriptorBase.Initialize = function(self)
	local seed = self.seed or 0
	local RandomGen = Random.new(seed)

	local offsetX,offsetY

	if seed ~= 0 then
		offsetX = RandomGen:NextInteger(0,self.max_random_octave_offset)
		offsetY = RandomGen:NextInteger(0,self.max_random_octave_offset)
	else
		offsetX = 0
		offsetY = 0
	end

	self:UpdateStatus("Generating heightmap")

	local HeightmapGeneratorObj = HeightmapGenerator.new()
	local Heightmap = HeightmapGeneratorObj:Generate(offsetX,offsetY,self.width,self.height,32)

	Falloff.Apply(Heightmap, self.falloff_distance)
	ApplyHeightmapSteps(Heightmap, self.tile_height_step)

	self:UpdateStatus("Generating rivers and lakes")

	local RiverGeneratorObj = RiverGenerator.new()
	RiverGeneratorObj.seed = self.seed
	RiverGeneratorObj.water_level = self.water_level

	local rivers, lakes = RiverGeneratorObj:Generate(Heightmap, 5, self.water_level, seed)
	ApplyRiversToHeightmap(Heightmap, self.tile_height_step, rivers)

	self:UpdateStatus("Generating trees")

	local trees = {}

	--what
	if self.forests_enabled then

		for x = 1, self.width * self.huge_tile_size_coeff do
			trees[x] = {}
		end

		local forest_points = {}

		do
			local forest_grid = {}

			local forest_grid_falloff = math.ceil(self.falloff_distance / self.forest_deduplication_grid_scale)

			for i = forest_grid_falloff, self.width/self.forest_deduplication_grid_scale - forest_grid_falloff do
				forest_grid[i] = {}
			end

			for point = 1, self.forest_point_amount do
				local pointX, pointY

				repeat
					pointX = RandomGen:NextInteger(forest_grid_falloff, self.width/self.forest_deduplication_grid_scale - forest_grid_falloff)
					pointY = RandomGen:NextInteger(forest_grid_falloff, self.height/self.forest_deduplication_grid_scale - forest_grid_falloff)
				until not (forest_grid[pointX][pointY])

				forest_grid[pointX][pointY] = true
			end

			local offsetMax = self.forest_deduplication_grid_scale * self.huge_tile_size_coeff - 1

			for pointX, row_x in pairs(forest_grid) do
				for pointY, val in pairs(row_x) do

					local offsetX = RandomGen:NextInteger(0, offsetMax)
					local offsetY = RandomGen:NextInteger(0, offsetMax)

					local finalX = pointX * self.forest_deduplication_grid_scale * self.huge_tile_size_coeff + offsetX
					local finalY = pointY * self.forest_deduplication_grid_scale * self.huge_tile_size_coeff + offsetY

					forest_points[#forest_points+1] = {finalX, finalY}

				end
			end
		end

		local forest_max_chance = self.forest_base_chance + 1
		local forest_min_height = self.water_level

		if self.forest_min_height > self.water_level then
			forest_min_height = self.forest_min_height
		end

		for x = 1, self.width * self.huge_tile_size_coeff do

			local HugeTileX = math.ceil(x / self.huge_tile_size_coeff)

			for y = 1, self.height * self.huge_tile_size_coeff do

				local HugeTileY = math.ceil(y / self.huge_tile_size_coeff)

				local Height = Heightmap[HugeTileX][HugeTileY]

				if Height >= self.forest_min_height and Height < self.forest_max_height then
					if (not (rivers[HugeTileX] and rivers[HugeTileX][HugeTileY])) and
					   (not (lakes[HugeTileX] and lakes[HugeTileX][HugeTileY])) then

						local chance = self.forest_base_chance

						for _,point in pairs(forest_points) do
							local x_diff = math.abs(x - point[1])
							if x_diff <= (self.forest_point_radius) then

								local y_diff = math.abs(y - point[2])
								if y_diff <= (self.forest_point_radius) then

									local new_radius = math.sqrt(x_diff^2 + y_diff^2)
									local new_chance = (1 - new_radius / self.forest_point_radius + self.forest_base_chance) / forest_max_chance

									if new_chance > chance then
										chance = new_chance
									end

								end

							end
						end

						local roll = RandomGen:NextNumber(0,1)
						if roll <= chance then
							trees[x][y] = true
						end

					end
				end

			end
		end

	end

	self.Heightmap = Heightmap
	self.trees = trees
	self.rivers = rivers
	self.lakes = lakes

	self:UpdateStatus("Initialized")
end

function TerrainDescriptorBase.get_water_surface_level(self,x,y)
	if self.rivers[x] and self.rivers[x][y] then
		return self.rivers[x][y]
	end

	if self.lakes[x] and self.lakes[x][y] then
		return self.lakes[x][y]
	end

	if self.Heightmap[x] and self.Heightmap[x][y] and self.Heightmap[x][y] < self.water_level then
		return self.water_level
	end

	return nil
end

return setmetatable(
	{new = object_constructor},
	{__newindex = function()
		error("TerrainDescriptor: Attempt to edit a read-only wrapper! Make sure that you create a new object first")
	end}
)
