Engine.LoadLibrary("rmgen");
Engine.LoadLibrary("rmgen-common");

TILE_CENTERED_HEIGHT_MAP = true;
var heightLand = 30;


var civilCentre = "skirmish/structures/default_civil_centre";
var citizen_female = "skirmish/units/default_support_female_citizen";
var infantry_melee = "skirmish/units/default_infantry_melee_b";
var infantry_ranged = "skirmish/units/default_infantry_ranged_b";

var mine_stone = "gaia/rock/savanna_large";
var mine_metal = "gaia/ore/savanna_large";

var texture_default = ["grid_subdiv"];

var g_Map = new RandomMap(heightLand, texture_default);
setSkySet("stratus");
setSunRotation(randomAngle());
setSunElevation(0.5 * Math.PI);

const numPlayers = getNumPlayers();
const mapCenter = g_Map.getCenter();
var mapSize = g_Map.getSize();

var listOfCivilcenters = [];
var listOfMines = [];

var rotate = function(pos,playerIndex) {
	/* rotate an element circular according to the players index
	 returns the actual position for the player */
	if (typeof playerIndex == 'undefined') {
		playerIndex = 0;
	}
	var wOffset = -0.25 * Math.PI;
	wOffset = 0;
	var w = 2 * Math.PI * (playerIndex - 1) / numPlayers + wOffset;
	var xTrans = 0.5*mapSize + pos.x*Math.cos(w)-pos.y*Math.sin(w);
	var yTrans = 0.5*mapSize + pos.x*Math.sin(w)+pos.y*Math.cos(w);
	return {'x': xTrans, 'y': yTrans};
}

var randomS = function(v) {
	if (Math.random() < 0.5) v = -v;
	return v;
}

const Map = function (parSize, parPlayer) {
	var size = parSize;
	this.getSize = () => size;
	var player = parPlayer;
	this.getPlayer = () => player;
	var topoFunctions = [];
	this.shape = (f) => { topoFunctions.push(f); };
	var townCenters = null;
	this.setTownCenters = (t) => { townCenters = t; t.setMap(this); };
	var textureFunction = null;
	this.setTexture = (t) => {
		textureFunction = t;
		t.setMap(this); };
	var minesFunction = null;
	this.setMines = (mi) => {
		minesFunction = mi;
	};
	
	this.moveAwayFromCliff = (y,x) => {
		// move elements away from the cliffs
		// first from the very steep cliffs (mTrigger)
		// and second further away (mDist)
		var halfMap = 0.5 * size;
		var mTrigger = [0.8,0.6,0.4];
		var mDist = [3,5];
		var worseValue = -1;
		var logTrace = [];
		for (let ti = 0; ti < mTrigger.length; ti++) {
			for (let mi = 0; mi < mDist.length; mi++) {
				var b = true;
				var maxRepeat = 4;
				while (b) {
					b = false;
					var value = gethYX(halfMap + y, halfMap + x);
					var diff = [];
					for (let did = 0; did < this.surp.length; did++) {
						var maxDiff = 0;
						// g_Map.log("Test Cliff?: " + this.sur[did].join('; '))
						for (let di = 1; di < mDist[mi]; di++) {
							var pCx = halfMap + x + di*this.surp[did][0];
							var pCy = halfMap + y + di*this.surp[did][1];
							// g_Map.log("Test Cliff!: " + did + " " + di)
							if (pCx >= 0 && pCx < size && pCy >= 0 && pCy < size) {
								// g_Map.log("Test Cliff?: " + did + " " + di)
								let pValue = gethYX(pCy,pCx);
								pValue = pValue % 1 - value;
								maxDiff = Math.max(maxDiff, Math.abs(pValue));
							}
						}
						diff.push(maxDiff);
					}
					// g_Map.log("Test Cliff: " + diff.join('; ') + ", iter:" + maxRepeat);
					var worseIndex = -1;
					var bestIndex = -1;
					worseValue = -1;
					var bestValue = 10;
					for (let i = 0; i < diff.length; i++) {
						if (diff[i] > worseValue) {
							worseValue = diff[i];
							worseIndex = i;
						}
						if (diff[i] < bestValue) {
							bestValue = diff[i];
							bestIndex = i
						}
					}
					if (worseValue > mTrigger[ti]) {
						maxRepeat--;
						logTrace.push(worseIndex);
						logTrace.push(bestIndex);
						if (0 < maxRepeat) {
							b = true;
						}
						// g_Map.log("Move away from Cliff:" + bestIndex);
						x = x - this.surp[worseIndex][0];
						y = y - this.surp[worseIndex][1];
						x = x + this.surp[bestIndex][0];
						y = y + this.surp[bestIndex][1];
					}
				}
			}
		}
		if (worseValue > 0.5) {
			g_Map.log("Move away from Cliff failed:" + worseValue);
		}
		return {'x': x, 'y': y, 'worseValue': worseValue};
	}

	this.getTreesInner = () => {
		var result = [];
		if (textureFunction != null) {
			result = textureFunction.getTreesInner();
		}
		return result;
	}

	this.getStoneMine = () => {
		var result = [];
		if (textureFunction != null) {
			result = textureFunction.getStoneMine();
		}
		return result;
	}

	this.getMetalMine = () => {
		var result = [];
		if (textureFunction != null) {
			result = textureFunction.getMetalMine();
		}
		return result;
	}

	// surrounding area
	this.sur = [[-1,0], [0,-1], [1,0], [0,1]];
	this.surp = [[-1,0], [-1,-1], [0,-1], [1,-1], [1,0], [1,1], [0,1], [-1,1]];
	
	// cache, get the Topo of a koordinate
	const getYX = (y,x) => {
		// calculating the height with all the formulas
		var value = 0;
		if (y < 0) y = 0;
		if (size <= y) y = size - 1;
		if (x < 0) x = 0;
		if (size <= x) x = size - 1;
		var px = x - 0.5 * size;
		var py = y - 0.5 * size;
		// d = Math.sqrt(px * px + py * py);
		// w = 0.5 * Math.atan2(py,px) / Math.PI;
		// const v = Math.floor(255 * y / sizeR);
		for (var i = 0; i < topoFunctions.length; i++) {
			value = topoFunctions[i].topo(py,px);
		}
		// @TODO for topo around Towncenters
		if (townCenters != null) {
			var dFC = townCenters.minDist(py,px);
			value = value * Math.min(1, dFC * dFC / 600);
		}
		return Math.abs(value) % 1;
	} 
		
	const gethYX = (y,x) => {
		// first look in the cache, before calculating the formulas
		var value = 0;
		if (y < 0) y = 0
		if (size <= y) y = size - 1
		if (x < 0) x = 0
		if (size <= x) x = size - 1
		if (x < hmap[y].length) {
			value = hmap[y][x];
			// value = getYX(y,x);
		}
		else {
			value = getYX(y,x);
		}
		return value;
	}
	
	// cache, saves heights for a certain stripe arround the actual y-coordinate
	const hmap = [];
	for (let y = 0; y < size; y++) {
		hmap.push([]);
	}
	
	// create a stripe in the cacheMap
	const hmapYCreate = (y) => {
		if (y >= 0 && y < size && hmap[y].length == 0) {
			while (hmap[y].length < size) {
				hmap[y].push(0)
			}
			for (let x = 0; x < size; x++) {
				hmap[y][x] = getYX(y,x);
			}
		}
	}

	this.getTopoMinMax = (y,x,value) => {
		// topographic differences by distance around a coordinate
		var minDiff = [];
		var maxDiff = [];
		for (let di = 1; di < 5; di++) {
			var localMin = 1000;
			var localMax = -1000;
			for (let did = 0; did < this.sur.length; did++) {
				var pCx = x + di*this.sur[did][0];
				var pCy = y + di*this.sur[did][1];
				if (pCx >= 0 && pCx < size && pCy >= 0 && pCy < size) {
					let pValue = gethYX(pCy,pCx);
					pValue = pValue % 1 - value;
					localMin = Math.min(localMin, pValue);
					localMax = Math.max(localMax, pValue);
				}
			}
			minDiff.push(localMin);
			maxDiff.push(localMax);
		}
		return {'topoMinDiff': minDiff, 'topoMaxDiff': maxDiff };
	}
	
	this.create = () => {
		for (var i = 0; i < topoFunctions.length; i++) {
			// initialize the topographic functions
			// setting size and players
			topoFunctions[i].setSize(size);
			topoFunctions[i].setPlayer(player);
		}
		
		if (townCenters != null) {
			// calculate the positions of the towncenters
			townCenters.calc();
		}

		if (minesFunction != null) {
			// calculate the positions of the mines
			minesFunction.setMap(this);
			minesFunction.calc();
		}
		
		for (let y = 0; y < size; y++) {
			// setting the cache
			if (y > 5) hmap[y-6] = [];
			hmapYCreate(y);
			hmapYCreate(y + 1);
			hmapYCreate(y + 2);
			hmapYCreate(y + 3);
			hmapYCreate(y + 4);
			hmapYCreate(y + 5);
			for (let x = 0; x < size; x++) {
				var px = x - 0.5 * size;
				var py = y - 0.5 * size;
				var pos = {"x":x, "y":y};
				var d = Math.sqrt(px * px + py * py);
				var w = 0.5 * Math.atan2(py,px) / Math.PI;

				var value = gethYX(y,x);
				value = value % 1;

				var texture = "grid_white";
				var item = null;

				var distFromTownCenter = 1000;
				var distFromMine = 1000;
				if (townCenters != null) {
					// distance from the next towncenter
					var distFromTownCenter = townCenters.minDist(py,px);
					// if (distFromTownCenter < 20) {
					// 	texture = "aegean_paving_01";
					// }
				}

				if (minesFunction != null) {
					var distFromMine = minesFunction.minDist(py,px);
				}

				if (textureFunction != null) {
					var {texture, item} = textureFunction.getTexture(y,x,value,distFromTownCenter, distFromMine);
				}

				if (false) {
					var worseValue = this.moveAwayFromCliff(py,px);
					texture  = "grid_white";
					if (worseValue > 0.3) {
						texture  = "yellow";
					}
					if (worseValue > 0.5) {
						texture  = "green";
					}
				}
				g_Map.setTexture(pos, texture);

				var height = Math.floor(heightLand + 20 * value);
				g_Map.setHeight(pos, height);

				if (item != null) {
					g_Map.placeEntityAnywhere(item, 0, pos, randomAngle());
				}
			}
		}
		if (townCenters != null) { townCenters.setCenters(); }
		if (minesFunction != null) { minesFunction.setMines(); }
		return this;
	}
}

const Topo = function (parAmplitude) {
	// superclass for all topology functions
	this.size = 128;
	this.player = 0;
	// some functions like max operate on subfunctions
	//		max, min	Maximum, Minimum of all subfunctions
	//		mul, div 	multiply, inverse multiply, of all subfunctions
	//		contrast 	with additional parameters, ...
	//		add			@TODO sum of all subfunctions
	//		alternate	@TODO f1 - f2 + f3 - f4
	//		parContrast	@TODO two functions second functions regulates contrast of first function
	this.subFunctions = [];
	this.distorsion = [];
	
	this.setSize = (r) => { this.size = r; }
	this.setPlayer = (p) => {
		this.player = p;
		for (let i = 0; i < this.subFunctions.length; i++) {
			this.subFunctions[i].setPlayer(this.player);
		}
	}
	this.addFun =  (f) => {
		f.setSize(this.size);
		f.setPlayer(this.player);
		this.subFunctions.push(f); }
	this.addDistorsion =  (f) => { this.distorsion.push(f); }
	
	this.calcDistorsion = (y,x) => {
		for (let i = 0; i < this.subFunctions.length; i++) {
			this.subFunctions[i].setPlayer(this.player);
			this.subFunctions[i].setSize(this.size);
		}
		for (let i = 0; i < this.distorsion.length; i++) {
			this.distorsion[i].setPlayer(this.player);
			this.distorsion[i].setSize(this.size);
		}
		for (let i = 0; i < this.distorsion.length; i++) {
			var {y,x} = this.distorsion[i].topo(y,x);
		}
		return {'y': y, 'x': x};
	}
}

const TopoRings = function (parAmplitude) {
	var amplitude = parAmplitude;
	Topo.call(this);
	
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var d = Math.sqrt(x * x + y * y);
		d = (d / amplitude) % 1;
		return d;
	}
}

const TopoCenterHole = function (parDistance, parOffset = 0) {
	var distance = parDistance;
	var offset = parOffset;
	Topo.call(this);

	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var d = Math.sqrt(x * x + y * y);
		d = Math.min(Math.max(0.0, d - parOffset) / distance,1.0) * Math.PI;
		var w = 0.5 - 0.5 * Math.cos(d);
		w = Math.max(0,Math.min(0.999,w));
		return w;
	}
}

const TopoPie = function (parMultiplier = 1, parOffset = 0) {
	var multiplier = parMultiplier;
	var Offset = parOffset;
	Topo.call(this);
	
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		// d = Math.sqrt(x * x + y * y);
		var w = multiplier * this.player * 0.5 * (parOffset + Math.atan2(y,x) / Math.PI);
		while (w < 0) w++;
		w = Math.max(0,Math.min(0.999,w % 1) );
		return w;
	}
}

const TopoCos = function (parMultiplier=1.0, parAmplitude=1.0, parOffset=0.0) {
	var multiplier = parMultiplier;
	var amplitude = parAmplitude;
	var offset = parOffset;
	Topo.call(this);

	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var d = Math.sqrt(x * x + y * y);
		var w = (0.5 + 0.5 * Math.cos((1+multiplier) * Math.PI * d / this.size) * amplitude + offset);
		//while (w < 0) w++;
		w = Math.max(0,Math.min(0.999,w));
		return w;
	}
}

const TopoSpiral = function (parAmplitude, parDamp = 1.0) {
	var amplitude = parAmplitude;
	var damp = parDamp;
	Topo.call(this);
	
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var d = Math.sqrt(x * x + y * y);
		damp = Math.min(Math.max(0.1 * d - parDamp,0),1);
		var w = this.player * 0.5 * Math.atan2(y,x) / Math.PI;
		w = (w + amplitude * 0.001 * d) % 1;
		while (w < 0) w++;
		w = Math.min(w, damp);
		return w;
	}
}

const TopoSpiralLog = function (parAmplitude, parDamp = 1.0) {
	var amplitude = parAmplitude;
	var damp = parDamp;
	Topo.call(this);
	
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var d = Math.sqrt(x * x + y * y);
		damp = Math.min(Math.max(0.1 * d - parDamp,0),1);
		var w = this.player * 0.5 * Math.atan2(y,x) / Math.PI;
		w = (w + amplitude * 0.1 * Math.log(1 + d)) % 1;
		while (w < 0) w++;
		w = Math.min(w, damp);
		return w;
	}
}

const TopoSpiral2 = function (amplitude, parDamp = 1.0, longitude = 4.0, cliffLengthFactor = 0.51) {
	this.amplitude = amplitude;
	this.damp = parDamp;
	this.longitude = longitude;
	// gap between two cliffs
	//	bigger numbers, bigger gap
	//	0.5 two cliffs join
	this.cliffLengthFactor = cliffLengthFactor;
	var cliffMulFactor = 1 / (1 - this.cliffLengthFactor);
	Topo.call(this);
	this.f = (x) => {return x / 100; };

	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var d = Math.sqrt(x * x + y * y);
		// wichtung
		var pd1 = Math.sqrt(d) / this.longitude;
		var pd2 = Math.floor(pd1);
		var pd3 = pd1 - pd2;
		var mul = 0.5;
		for (let i=0; i < pd2; i++) {
			mul *= 2;
		}
		var damp = Math.min(Math.max(0.1 * d - this.damp,0),1);
		var w1 = (mul * this.player * 0.5 * Math.atan2(y,x) / Math.PI);
		w1 = w1 + this.amplitude * 0.1 * this.f(1 + d);
		while (w1 < 0) w1++;
		w1 = Math.min(w1 % 1,0.999);
		var w2 = mul * this.player * Math.atan2(y,x) / Math.PI;
		w2 = w2 + this.amplitude * 0.1 * this.f(1 + d);
		while (w2 < 0) w2++;
		w2 = Math.min(w2 % 1,0.999);
		// var w = w1*(1-pd3)*(1-pd3) + w2*pd3*pd3;
		var w = w1*Math.sqrt(cliffMulFactor * Math.max(1-pd3-this.cliffLengthFactor,0)) + w2*Math.sqrt(cliffMulFactor * Math.max(pd3-this.cliffLengthFactor,0));
		while (w < 0) w++;
		w = Math.min(w % 1, damp,0.999);
		// w = Math.min(w % 1, 0.999);
		return w;
	}
}

const TopoSpiralLog2 = function (amplitude, parDamp = 1.0, longitude = 4.0, cliffLengthFactor = 0.51) {
	TopoSpiral2.call(this, amplitude, parDamp, longitude, cliffLengthFactor);
	this.f = (x) => {return Math.log(1 + x)};
}

const TopoSpiralSin2 = function (amplitude, parDamp = 1.0, longitude = 4.0, cliffLengthFactor = 0.51) {
	TopoSpiral2.call(this, amplitude, parDamp, longitude, cliffLengthFactor);
	this.f = (x) => {return Math.log(1 + x) * Math.sin(x / 40); };
}

const TopoDivide = function (parAmplitude, parOffset = 0) {
	var amplitude = parAmplitude;
	var offset = parOffset;
	Topo.call(this);
	
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var d = Math.sqrt(x * x + y * y);
		var dd = Math.floor(d / amplitude) + 1
		var w = this.player * 0.5 * (Math.atan2(y,x) / Math.PI - 0) + offset * dd;
		w = Math.abs(Math.sin(Math.PI * d / amplitude) * Math.cos(w * dd * Math.PI));
		return w;
	}
}

const FilterMask = function (factor=10,bias=0) {
	// normally for one function
	// create a sharp structure of heighs and lows
	// idea is for making masks
	Topo.call(this);
	this.factor = factor;
	// bias
	//	-1.5	result is high = 1
	//	4...	result is mostly low = 0
	this.bias = bias;

	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 0;
		for (let i=0; i < this.subFunctions.length; i++ ) {
			var s = Math.abs(2 * this.subFunctions[i].topo(y,x) - 1);
			s = Math.min(0.999,Math.max(0, this.factor*(1 - (2+this.bias)*s)));
			w = Math.max(w, s);
		}
		// w = Math.max(0, w * offset - offset + 1)
		return w;
	}
}

const FilterCoarse = function (factor = 10, offset = 0) {
	// needs two function,
	// the input, and the function to determine the contrast
	Topo.call(this);
	this.factor = factor;
	this.offset = offset;

	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 0;
		if (this.subFunctions.length == 2) {
			var f = this.factor * Math.min(0.999,Math.max(0,this.subFunctions[1].topo(y,x)));

			w = Math.min(0.999,Math.max(0,f * this.subFunctions[0].topo(y,x) + 0.5*(1.0 + this.offset -f)));
			// s = Math.min(0.999,Math.max(0, this.factor*(1 - (2+this.bias)*s)));

		// w = Math.max(0, w * offset - offset + 1)
		}
		return w;
	}
}

const FilterContrast = function (lowValue = 0, highValue = 1) {
	// does take only one function
	Topo.call(this);
	this.lowValue = lowValue;
	this.highValue = highValue;
	this.offset = this.lowValue / (this.lowValue - this.highValue);
	this.rate = 1 / (this.highValue - this.lowValue)

	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 0;
		if (this.subFunctions.length == 1) {
			w = this.subFunctions[0].topo(y,x) * this.rate + this.offset;
			w = Math.min(0.999, Math.max(0, w));
		}
		return w;
	}
}

const FilterCosContrast = function (parAmplitude = 0) {
	// does take only one function
	var amplitude = Math.floor(parAmplitude);
	Topo.call(this);
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 0;
		if (this.subFunctions.length == 1) {
			w = this.subFunctions[0].topo(y,x);
			w = 0.5 - 0.5 * Math.cos(w * (1.0 + 2.0 * amplitude) * Math.PI);
			w = Math.min(0.999, Math.max(0.0, w));
		}
		return w;
	}
}

const FilterMax = function (parOffset = 1) {
	var offset = parOffset;
	Topo.call(this);
	
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 0;
		for (let i=0; i < this.subFunctions.length; i++ ) {
			w = Math.max(w, this.subFunctions[i].topo(y,x));
		}
		w = Math.max(0, w * offset - offset + 1)
		return w;
	}
}

const FilterMul = function (parOffset = 1) {
	var offset = parOffset;
	Topo.call(this);
	
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 1;
		for (let i=0; i < this.subFunctions.length; i++ ) {
			w = w * Math.abs(this.subFunctions[i].topo(y,x));
		}
		w = Math.min(0.999,Math.max(0, w * offset - offset + 1))
		return w;
	}
}

const FilterMul2 = function (parFactor = 1) {
	var parFactor = parFactor;
	Topo.call(this);

	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 0;
		for (let i=0; i < this.subFunctions.length; i++ ) {
			var sf = this.subFunctions[i].topo(y,x);
			var ws = Math.min(Math.max(-1.0, 2 * (sf - w)),1.0) * 0.5 + 0.5;
			w = ws * sf + (1 - ws) * w;
		}
		w = Math.max(0.0, Math.min(w,0.999));
		return w;
	}
}

const FilterDiv = function (parOffset = 1) {
	var offset = parOffset;
	Topo.call(this);
	
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 1;
		for (let i=0; i < this.subFunctions.length; i++ ) {
			w = w * Math.abs(1 - this.subFunctions[i].topo(y,x));
		}
		w = Math.max(0, (1 - w) * offset - offset + 1);
		return w;
	}
}

const FilterMin = function (parOffset = 1) {
	var offset = parOffset;
	Topo.call(this);
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 1;
		for (let i=0; i < this.subFunctions.length; i++ ) {
			w = Math.min(w, this.subFunctions[i].topo(y,x));
		}
		w = Math.max(0, w * offset - offset + 1);
		return w;
	}
}

const FilterSub = function () {
	Topo.call(this);

	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 0;
		var p = 1;
		for (let i=0; i < this.subFunctions.length; i++ ) {
			w = w + p * this.subFunctions[i].topo(y,x);
			p = -p;
		}
		w = Math.min(Math.max(0, w),0.999);
		return w;
	}
}

const FilterContrast2 = function (parMin = 0.25, parMax = 0.0) {
	var valMin = parMin;
	var valMax = parMax;
	Topo.call(this);
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 1;
		for (let i=0; i < this.subFunctions.length; i++ ) {
			w = Math.min(w, this.subFunctions[i].topo(y,x));
		}
		w = Math.min(1.0,Math.max(0.0, (1.0 + valMax + valMin) * w - valMin));
		return w;
	}
}

const FilterSquare = function () {
	Topo.call(this);
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 1;
		for (let i=0; i < this.subFunctions.length; i++ ) {
			w = Math.min(w, this.subFunctions[i].topo(y,x));
		}
		w = w * w;
		return w;
	}
}

const FilterSaw = function () {
	Topo.call(this);
	this.topo = (y,x) => {
		var {y,x} = this.calcDistorsion(y,x);
		var w = 0;
		for (let i=0; i < this.subFunctions.length; i++ ) {
			w += Math.abs(this.subFunctions[i].topo(y,x));
		}
		w = Math.min(Math.abs(2 * (w % 2) - 1), 0.999);
		return w;
	}
}

const DistortDiv = function (parAmplitude, parOffset = 0, parGrad = 0.5 * Math.PI) {
	var amplitude = parAmplitude;
	var offset = parOffset;
	var grad = parGrad;
	var mul = 1;
	Topo.call(this);
	
	this.topo = (y,x) => {
		var d = Math.sqrt(x * x + y * y);
		var dd = Math.floor(d / amplitude) + 1
		var w = (Math.floor(mul * dd * this.player * 0.5 * Math.atan2(y,x) / Math.PI + 0.5 + offset * dd * dd) - offset * dd * dd) / (mul * this.player * dd);
		// Distance from zero
		var parD = (Math.floor(d / amplitude) + 0.5) * amplitude;
		var parW = 2 * Math.PI * w;
		// Coordinates for local Distorsioncenter
		var px = parD * Math.cos(parW);
		var py = parD * Math.sin(parW);
		
		// distance + angle
		var dx = x - px;
		var dy = y - py;
		var rd = Math.sqrt(dx*dx + dy*dy);
		w = 0.5 * Math.atan2(y,x) / Math.PI;
		
		// Strength
		var a = mul * this.player * 0.5 * (Math.atan2(y,x) / Math.PI) + offset * dd;
		a = Math.abs(Math.sin(Math.PI * d / amplitude) * Math.cos(a * dd * Math.PI));
		// a = a * a;
		a = 0.7 * (Math.exp(a) - 1);
		
		if (dd % 2 == 0) {
			a = -a;
		}
		
		var ddx = px + (dx * Math.cos(a * grad) - dy * Math.sin(a * grad));
		var ddy = py + (dx * Math.sin(a * grad) + dy * Math.cos(a * grad));
		return {'y': ddy, 'x': ddx};
	}
}

const DistortSquare = function (parAmplitude, parOffset = 0) {
	var amplitude = parAmplitude;
	var offset = parOffset;
	var grad = 0.5 * Math.PI;
	Topo.call(this);
	
	this.topo = (y,x) => {
		var amp = amplitude;
		var amp2 = Math.PI / amplitude / 2;
		var dv = Math.floor(0.5 * x / amp + 0.5);
		var dvy = Math.floor(y / amp + 0.5);
		var ddv = dv * amp;
		var mx = Math.cos(x * amp2);
		var my = Math.cos((y + offset * ddv ) * amp2);
		var a = Math.abs(mx * my);
		
		// a = Math.max((Math.exp(a) - 2),0);
		// a = a * a;
		
		var px = 2 * Math.floor(0.5 * x / amp + 0.5) * amp;
		var py = 2 * Math.floor(0.5 * y / amp + 0.5 + 0.5 * dv * offset) * amp - offset * ddv;
		
		var dx = x - px;
		var dy = y - py;
		var rd = Math.sqrt(dx*dx + dy*dy);
		var w = 0.5 * Math.atan2(y,x) / Math.PI;
		
		// if ((dv) % 2 == 0) { a = -a; }
		
		var ddx = px + (dx * Math.cos(a * grad) - dy * Math.sin(a * grad));
		var ddy = py + (dx * Math.sin(a * grad) + dy * Math.cos(a * grad));
		return {'y': ddy, 'x': ddx};
	}
}

const Mines = function() {
	this.map = null;
	this.minesStone = [];
	this.minesMetal = [];
	this.setMap = (m) => { this.map = m; }

	this.minesLoopEnd = () => {
		const size = this.map.getSize();
		return size / 2 - 16 - size / 8;
	}

	this.calc = () => {
		const player = this.map.getPlayer();
		const size = 0.5 * this.map.getSize();
		const offset = 0.3 * Math.PI / player;
		var wOffset = 0;
		var start = 25;
		const step = 25;
		var badValues = 0;
		while (start < this.minesLoopEnd()) {
			for (let i = 0; i < player; i++) {
				var w1 = 2 * Math.PI * (i + offset + wOffset) / player;
				var w2 = 2 * Math.PI * (i - offset + wOffset) / player;
				// console.log(d,w);
				var px1 = Math.round(start * Math.cos(w1));
				var py1 = Math.round(start * Math.sin(w1));

				var px2 = Math.round(start * Math.cos(w2));
				var py2 = Math.round(start * Math.sin(w2));
				var {y,x,worseValue} = this.map.moveAwayFromCliff(py1,px1);
				if (worseValue > 0.5) {
					badValues++;
				}
				this.minesStone.push({'x':x, 'y':y});
				// this.minesStone.push({'x':px1, 'y':py1});
				var {y,x,worseValue} = this.map.moveAwayFromCliff(py2,px2);
				if (worseValue > 0.5) {
					badValues++;
				}
				this.minesMetal.push({'x':x, 'y':y});
				// this.minesMetal.push({'x':px2, 'y':py2});
			}
			start += step;
			wOffset += 0.2;
		}
		g_Map.log("Mines to close to cliff: " + badValues)
	}

	this.minDist = (y,x) => {
		var result = 500;
		for (let i = 0; i < this.minesStone.length; i++) {
			var dx = this.minesStone[i].x - x;
			var dy = this.minesStone[i].y - y;
			result = Math.min(result, Math.sqrt(dx*dx + dy*dy));
		}
		for (let i = 0; i < this.minesStone.length; i++) {
			var dx = this.minesMetal[i].x - x;
			var dy = this.minesMetal[i].y - y;
			result = Math.min(result, Math.sqrt(dx*dx + dy*dy));
		}
		return result;
	}

	this.setMines = () => {
		var mine_stone = this.map.getStoneMine();
		var mine_metal = this.map.getMetalMine();
		var cc = 0.5 * this.map.getSize();
		for (let i = 0; i < this.minesStone.length; i++) {
			const x = this.minesStone[i].x;
			const y = this.minesStone[i].y;
			// g_Map.log("Mine set: " + i + " " + (x + cc) + " " + (y + cc) + " " + mine_stone);
			g_Map.placeEntityAnywhere(mine_stone, 0, {"x": cc + x, "y": cc + y}, randomAngle());
		}
		for (let i = 0; i < this.minesMetal.length; i++) {
			const x = this.minesMetal[i].x;
			const y = this.minesMetal[i].y;
			// g_Map.log("Mine set: " + i + " " + (x + cc) + " " + (y + cc) + " " + mine_metal);
			g_Map.placeEntityAnywhere(mine_metal, 0, {"x": cc + x, "y": cc + y}, randomAngle());
		}
	}

}

const TownCenter = function() {
	this.distanceFromEdge = 20;
	this.townCenter = [];
	this.height = [];
	this.map = null;
	
	this.setMap = (m) => { this.map = m; }
	this.setDistanceFromEdge = (m) => { this.distanceFromEdge = m; }
	
	this.minDist = (y,x) => {
		// get the distance of y,x from the closest towncenter
		var result = 500;
		for (let i = 0; i < this.townCenter.length; i++) {
			var dx = this.townCenter[i].x - x;
			var dy = this.townCenter[i].y - y;
			result = Math.min(result, Math.sqrt(dx*dx + dy*dy)); 
		}
		return result;
	}
	
	this.setCenters = () => {
		g_Map.log("TownCenter set");
		var cc = 0.5 * this.map.getSize();
		var mine_stone = this.map.getStoneMine();
		var mine_metal = this.map.getMetalMine();
		var trees = this.map.getTreesInner();
		for (let i = 0; i < this.townCenter.length; i++) {
			var x = this.townCenter[i].x;
			var y = this.townCenter[i].y;
			// g_Map.log("TownCenter set: " + i + " " + x + " " + y);
			// console.log(this.townCenter[i], x,y);
			var w = Math.atan2(y,x);
			
			var civilCentre = "skirmish/structures/default_civil_centre";
			var citizen_female = "skirmish/units/default_support_female_citizen";
			var infantry_melee = "skirmish/units/default_infantry_melee_b";
			var infantry_ranged = "skirmish/units/default_infantry_ranged_b";

			// var mine_stone = "gaia/rock/savanna_large";
			// var mine_metal = "gaia/ore/savanna_large";
			// var trees = ["gaia/tree/oak", "gaia/tree/oak_holly", "gaia/tree/bush_temperate", "gaia/tree/oak", "gaia/tree/oak_holly", "gaia/tree/bush_temperate", "gaia/tree/oak_large", "gaia/tree/oak_new"];
			
			
			// angle depending from
			var angle = 0.5 * Math.PI * (Math.floor(2 * w / Math.PI + 0) + 2.5);
			var angle2 = angle + Math.PI;
			var entity = "skirmish/structures/default_civil_centre";
			g_Map.placeEntityAnywhere(entity, i+1, {"x": cc + x, "y": cc + y}, angle);
			
			var elems = [
				{ "x": 6.0, "y": 0.0, "elem": citizen_female },
				{ "x": 6.0, "y": -2.0, "elem": citizen_female },
				{ "x": 6.0, "y": -4.0, "elem": citizen_female },
				{ "x": 6.0, "y": -6.0, "elem": citizen_female },
				{ "x": -6.0, "y": 0.0, "elem": infantry_melee },
				{ "x": -6.0, "y": -2.0, "elem": infantry_ranged },
				{ "x": -6.0, "y": -4.0, "elem": infantry_melee },
				{ "x": -6.0, "y": -6.0, "elem": infantry_ranged },
				{ "x": 10.0, "y": 6.0, "elem": mine_stone },
				{ "x": -10.0, "y": 6.0, "elem": mine_metal }
			];
			
			for (var i1 = -6; i1 < 7; i1++ ) {
				elems.push({"x": i1, "y": 8, "elem": pickRandom(trees)});
				elems.push({"x": i1, "y": 9, "elem": pickRandom(trees)});
			}
			
			for (let ii = 0; ii < elems.length; ii++) {
				// var kkx = Math.cos(w) * elems[i].y - Math.sin(w) * elems[i].x;
				// var kky = Math.sin(w) * elems[i].y + Math.cos(w) * elems[i].x;
				var kkx = Math.cos(angle2) * elems[ii].y - Math.sin(angle2) * elems[ii].x;
				var kky = Math.sin(angle2) * elems[ii].y + Math.cos(angle2) * elems[ii].x;
				
				g_Map.placeEntityAnywhere(elems[ii].elem, i+1, {"x":cc + x + kkx, "y":cc + y + kky}, randomAngle());
			}
		}
	}
	
}

const TownCenterCircular = function(offset = 0) {
	// offset rotates towns around the center
	this.offset = offset;
	TownCenter.call(this);
	this.setMap = (m) => {
		this.map = m;
		this.distanceFromEdge = 5 / 48 * this.map.getSize() + 20 / 3;
	}
	
	this.calc = () => {
		// all towncenter in a circle with equal distance from outer edge, for multiplayer everyone against everyone else
		// minimum distance from the map-center 32 fields
		// offset is relative to number of players
		// console.log("calc", this.map.getPlayer());
		var d = Math.max(0.5 * this.map.getSize() - this.distanceFromEdge, 32);
		for (let i = 0; i < this.map.getPlayer(); i++) {
			var w = 2 * Math.PI * (i + this.offset) / this.map.getPlayer();
			// console.log(d,w);
			var px = Math.round(d * Math.cos(w));
			var py = Math.round(d * Math.sin(w));
			this.townCenter.push({'x':px, 'y':py, 'h': 0});
		}
	}
}

const Texture = function() {
	this.map = null;
	// when the texture is set to a map,
	// the map provides some essential date (size, amount player)
	// make sure all the elements get these infos
	this.setMapCallback = [];
	this.setMap = (m) => { this.map = m;
		if (this.forest != null) {
			this.forest.setSize(this.map.getSize());
			this.forest.setPlayer(this.map.getPlayer());
		}
		for (let i = 0; i < this.setMapCallback.length; i++) {
			this.setMapCallback[i].setSize(this.map.getSize());
			this.setMapCallback[i].setPlayer(this.map.getPlayer());
		}
	}
	this.trees_inner = ["gaia/tree/oak_new"];
	this.mine_stone = "gaia/rock/savanna_large";
	this.mine_metal = "gaia/ore/savanna_large";
	this.getTreesInner = () => this.trees_inner;
	this.getStoneMine = () => this.mine_stone;
	this.getMetalMine = () => this.mine_metal;

	// very common textureFeatures
	this.forest = null;
	this.setForest = (f) => { this.forest = f;
		if (this.map != null) {
			this.forest.setSize(this.map.getSize());
			this.forest.setPlayer(this.map.getPlayer());
		}
	};

	this.getTopoMinMax = (y,x,value) => {
		var {topoMinDiff, topoMaxDiff} = this.map.getTopoMinMax(y,x,value);
		return {'topoMinDiff': topoMinDiff, 'topoMaxDiff': topoMaxDiff };
		// return map.getTopoMinMax(y,x);
	}

	this.getTexture = (yPos,xPos,height, distanceClosestTown = 1000, distanceClosestMine = 1000) => {
		var {texture, item} = this._getTexture(yPos,xPos,height, distanceClosestTown, distanceClosestMine);
		return {'texture': texture, 'item': item};
	}

	this._getTexture = (yPos,xPos,height, distanceClosestTown, distanceClosestMine) => {
		var texture = "grid_subdiv";
		return {'texture': texture, 'item': null}
	};
}

const TextureHeight = function() {
	// colors according to height
	Texture.call(this);
	this._getTexture = (yPos,xPos,height, distanceClosestTown, distanceClosestMine) => {
		var texture = "grid_white";
		var item = null;
		var allowForest = 1;
		// if (height > 0.05) texture = "blackness";
		if (height > 0.05) texture = "blue";
		if (height > 0.1) texture = "gray";
		if (height > 0.2) texture = "brown";
		if (height > 0.3) texture = "green";
		if (height > 0.5) texture = "neon green";
		if (height > 0.7) texture = "yellow";
		if (height > 0.9) texture = "red";
		if (distanceClosestTown < 20) {
			allowForest = 0;
		}
		if (this.forest != null && allowForest > 0.1) {
			const valueForest = this.forest.topo(yPos - 0.5 * this.map.getSize(),xPos - 0.5 * this.map.getSize()) - Math.random();
			if (valueForest > 0) {
				if (valueForest > 0.7) {
					item = pickRandom(this.trees_inner);
				}
			}
		}
		return {'texture': texture, 'item': item}
	}
}

const TextureCliff1 = function() {
	// a preset for a most terrains
	Texture.call(this);
	// value between 0 and 1
	// smaller values spread the forests more, that means more trees
	this.valueForestSpread = 0.3;
	// value between 0 and 1
	// smaller values make forests more dense, that means more trees
	this.valueForestSet = 0.0;
	this.textureDefaultLow = ["grid_white"];
	this.textureDefaultHigh = ["grid_white"];
	this.trees_badland = this.trees_inner;
	this.badlands1Inner = ["red"];
	this.badlands1Outer = ["yellow"];
	this.textureTown = ["gray"];
	this.textureTownOuter = ["gray"];
	this.cliffOuter2 = ["brown"];
	this.cliffOuter1 = ["brown"];
	this.cliffSmall = ["green"];
	this.cliffUpper = ["blue"];
	this.cliffLower = ["light blue"];
	this.forestFloor = ["red"];
	this.forestFloorBadland = this.forestFloor;
	this.textureMine = ["red"];
	this.textureMineBadland = this.textureMine;
	this.badlands1 = null;

	this.setBadlands1 = (f) => { this.badlands1 = f;
		if (this.map != null) {
			this.badlands1.setSize(this.map.getSize());
			this.badlands1.setPlayer(this.map.getPlayer());
		}
		this.setMapCallback.push(f);
	};

	this._getTexture = (yPos,xPos,height, distanceClosestTown, distanceClosestMine) => {
		var texture = pickRandom(this.textureDefaultLow);
		if (Math.random() < height) {
			texture = pickRandom(this.textureDefaultHigh);
		}

		var item = null;
		var allowForest = 1;
		var isBadland = 0;

		if (this.badlands1 != null) {
			const valueBadlands1 = this.badlands1.topo(yPos - 0.5 * this.map.getSize(),xPos - 0.5 * this.map.getSize());
			// const valueForest = this.forest.topo(yPos - 0.5 * this.map.getSize(),xPos - 0.5 * this.map.getSize()) - Math.random();
			if (valueBadlands1 + 0.2*Math.random() > 0.5) {
				allowForest = 0;
				isBadland = 1;
				texture = pickRandom(this.badlands1Outer);
				if (valueBadlands1 + 0.3*Math.random() > 0.8) {
					texture = pickRandom(this.badlands1Inner);
				}
			}
		}

		if (distanceClosestTown < 20) {
			var r = Math.max(Math.min(1.0, 3.0 - 0.2 * distanceClosestTown),0.0);
			if (Math.random() < r) {
				texture = pickRandom(this.textureTownOuter);
				allowForest = 0;
			}
			var r = Math.max(Math.min(1.0, 2 - 0.2 * distanceClosestTown),0.0);
			if (Math.random() < r) {
				texture = pickRandom(this.textureTown);
				allowForest = 0;
			}
		}
		if (distanceClosestMine < 3) {
			if (isBadland) {
				texture = pickRandom(this.textureMineBadland);
			}
			else {
				texture = pickRandom(this.textureMine);
			}
			allowForest = 0;
		}
		var {topoMinDiff, topoMaxDiff} = this.getTopoMinMax(yPos, xPos,height);

		if (Math.abs(topoMinDiff[3]) > 0.4 || Math.abs(topoMaxDiff[3]) > 0.4) {
			texture = pickRandom(this.cliffOuter2);
			allowForest = 0;
		}
		if (Math.abs(topoMinDiff[2]) > 0.3 || Math.abs(topoMaxDiff[2]) > 0.3) {
			texture = pickRandom(this.cliffOuter2);
			allowForest = 0;
		}
		if (Math.abs(topoMinDiff[1]) > 0.2 || Math.abs(topoMaxDiff[1]) > 0.2) {
			texture = pickRandom(this.cliffOuter1);
			allowForest = 0;
		}
		if (Math.abs(topoMinDiff[0]) > 0.1 || Math.abs(topoMaxDiff[0]) > 0.1) {
			texture = pickRandom(this.cliffSmall);
			allowForest = 0;
		}
		if (Math.abs(topoMinDiff[0]) > 0.4) {
			texture = pickRandom(this.cliffUpper);
			allowForest = 0;
		}
		if (Math.abs(topoMaxDiff[0]) > 0.4) {
			texture = pickRandom(this.cliffLower);
			allowForest = 0;
		}
		if (this.forest != null && allowForest > 0.1) {
			const valueForest = this.forest.topo(yPos - 0.5 * this.map.getSize(),xPos - 0.5 * this.map.getSize()) - Math.random();

			if (valueForest > 0) {
				texture = pickRandom(this.forestFloor);
				if (valueForest > this.valueForestSpread && Math.random() > 0.5) {
					if (isBadland) {
						item = pickRandom(this.trees_badland);
					}
					else {
						item = pickRandom(this.trees_inner);
					}
				}
			}
		}
		return {'texture': texture, 'item': item}
	};
}

const TextureCliffPolar = function() {
	TextureCliff1.call(this);
	this.mine_stone = "gaia/rock/polar_01";
	this.mine_metal = "gaia/ore/polar_01";
	this.trees_inner = ["gaia/tree/fir_winter", "gaia/tree/pine_w", "gaia/tree/temperate_winter"];
	this.trees_badland = this.trees_inner;
	// this.textureDefault = ["polar_tundra", "polar_tundra_snow", "polar_snow_a", "polar_snow_b"];
	this.textureDefaultLow = ["polar_tundra", "polar_tundra_snow"];
	this.textureDefaultHigh = ["polar_tundra", "polar_tundra_snow"];
	this.badlands1Inner = ["ice_01", "polar_ice_snow", "ice_01", "polar_ice_snow", "polar_snow_a", "polar_snow_b"];
	this.badlands1Outer = ["polar_snow_a", "polar_snow_b"];
	this.textureTown = ["alpine_dirt", "alpine_dirt_grass_50", "new_alpine_citytile", "new_alpine_citytile"];
	this.textureTownOuter = ["alpine_dirt", "alpine_dirt_grass_50"];
	this.cliffOuter2 = ["polar_cliff_snow"];
	this.cliffOuter1 = ["polar_cliff_snow"];
	this.cliffSmall = ["polar_cliff_b", "polar_ice_snow"];
	this.cliffUpper = ["polar_cliff_snow", "polar_ice_snow"];
	this.cliffLower = ["polar_cliff_b"];
	//this.forestFloor = ["alpine_forrestfloor", "alpine_forrestfloor_snow"];
	this.forestFloor = ["forestfloor_dirty", "forestfloor_pine"];
	this.textureMine = ["clay_01", "clay_02"];
	this.textureMineBadland = ["clay_01", "clay_02", "ice_01", "polar_ice_snow", "ice_01", "polar_ice_snow", "polar_snow_a", "polar_snow_b"];
}

const TextureCliffAlpin1 = function() {
	TextureCliff1.call(this);
	this.mine_stone = "gaia/rock/alpine_large";
	this.mine_metal = "gaia/ore/alpine_large";
	this.trees_inner = ["gaia/tree/euro_beech", "gaia/tree/euro_birch", "gaia/tree/fir", "gaia/tree/fir_sapling", "gaia/tree/oak", "gaia/tree/oak_new", "gaia/tree/pine", "gaia/tree/pine_black", "gaia/tree/temperate"];
	this.trees_badland = this.trees_inner;
	this.textureDefaultLow = ["alpine_grass", "alpine_grass_a", "alpine_grass_b"];
	this.textureDefaultHigh = ["alpine_grass_c", "alpine_grass_d"];
	this.badlands1Inner = ["alpine_dirt"];
	this.badlands1Outer = ["alpine_dirt_grass_50"];
	this.textureTown = ["new_alpine_citytile", "temperate_rocks_dirt_01"];
	this.textureTownOuter = ["new_alpine_citytile", "dirt_brown_a", "dirt_brown_b"];
	this.cliffOuter2 = ["alpine_cliff_c"];
	this.cliffOuter1 = ["alpine_cliff_c"];
	this.cliffSmall = ["alpine_grass_rocky"];
	this.cliffUpper = ["alpine_cliff_a", "alpine_cliff_a"];
	this.cliffLower = ["alpine_cliff_a", "alpine_cliff_a"];
	//this.forestFloor = ["alpine_forrestfloor", "alpine_forrestfloor_snow"];
	this.forestFloor = ["alpine_grass_d", "alpine_grass_e", "alpine_grass_d_wild"];
	this.textureMine = ["new_alpine_grass_dirt_a"];
	this.textureMineBadland = this.textureMine;
	setSunColor(0.8, 0.8, 0.8);
	setSunElevation(0.5 * Math.PI);
	// setSkySet("overcast");
	setSkySet("stratus");
}

const TextureCliffAlpin2 = function() {
	TextureCliff1.call(this);
	this.mine_stone = "gaia/rock/alpine_large";
	this.mine_metal = "gaia/ore/alpine_large";
	// this.trees_inner = ["gaia/tree/euro_beech", "gaia/tree/euro_birch", "gaia/tree/fir", "gaia/tree/fir_sapling", "gaia/tree/oak", "gaia/tree/oak_new", "gaia/tree/pine", "gaia/tree/pine_black", "gaia/tree/temperate"];
	this.trees_inner = ["gaia/tree/fir", "gaia/tree/fir", "gaia/tree/fir_sapling", "gaia/tree/pine"];
	this.trees_badland = ["gaia/tree/fir_winter"];
	this.textureDefaultLow = ["alpine_dirt_grass_50"];
	this.textureDefaultHigh = ["alpine_dirt_grass_50"];
	this.badlands1Inner = ["alpine_snow_01", "alpine_snow_02", "alpine_snow_03", "alpine_dirt_snow"];
	this.badlands1Outer = ["alpine_dirt_snow"];
	// "clay_01" "new_alpine_citytile"
	this.textureTown = ["alpine_paving_stones_02"];
	this.textureTownOuter = ["alpine_paving_stones_02", "new_alpine_grass_dirt_a", "new_alpine_grass_mossy"];
	this.cliffOuter2 = ["alpine_shore_rocks_grass_50"];
	this.cliffOuter1 = ["alpine_shore_rocks", "alpine_shore_rocks_grass_50"];
	this.cliffSmall = ["alpine_cliff_a", "alpine_cliff_b", "alpine_mountainside"];
	this.cliffUpper = ["alpine_mountainside", "alpine_mountainside", "alpine_cliff"];
	this.cliffLower = ["alpine_cliff"];
	//this.forestFloor = ["alpine_forrestfloor", "alpine_forrestfloor_snow"];
	this.forestFloor = ["alpine_forrestfloor"];
	this.forestFloorBadland = this.forestFloor;
	this.textureMine = ["alpine_dirt"];
	this.textureMineBadland = ["alpine_dirt_snow"];
	setSunColor(0.8, 0.8, 0.8);
	setSunElevation(0.5 * Math.PI);
	// setSkySet("overcast");
	setSkySet("stratus");
}

const TextureCliffTemp1 = function() {
	TextureCliff1.call(this);
	this.mine_stone = "gaia/rock/alpine_large";
	this.mine_metal = "gaia/ore/alpine_large";
	// this.trees_inner = ["gaia/tree/euro_beech", "gaia/tree/euro_birch", "gaia/tree/fir", "gaia/tree/fir_sapling", "gaia/tree/oak", "gaia/tree/oak_new", "gaia/tree/pine", "gaia/tree/pine_black", "gaia/tree/temperate"];
	this.trees_inner = ["gaia/tree/temperate", "gaia/tree/pine"];
	this.trees_badland = [];
	this.textureDefaultLow = ["temperate_grass_02", "temperate_grass_03", "temperate_grass_04"];
	this.textureDefaultHigh = ["temperate_grass_02", "temperate_grass_03", "temperate_grass_04"];
	this.badlands1Inner = ["temperate_mud_01", "temperate_grass_dirt_01", "temperate_grass_mud_01"];
	this.badlands1Outer = ["temperate_grass_dirt_01"];
	// "aegean_paving_01", "aegean_paving_02"
	// "temperate_paving_01" "temperate_paving_02" "temperate_paving_03"
	// "alpine_paving_stones_01", "alpine_paving_stones_02"
	// "new_alpine_citytile"
	// "medit_city_tile", "medit_city_tile_02", "medit_city_tile_dirt", "medit_road_broken", "savanna_tile_a", "savanna_tile_a_red", "temp_road_muddy", "tropic_citytile_a"
	// "road1", "road2", "road_5", "road_flat", "road_med", "road_med_a", "road_muddy", "road_roman", "road_rome_a", "road_stones"
	this.textureTown = ["alpine_paving_stones_01"];
	this.textureTownOuter = ["alpine_grass_d", "alpine_grass_e", "temperate_mud_01"];
	this.cliffOuter2 = ["temp_dirt_gravel", "temp_dirt_gravel_b", "temperate_grass_05", "temperate_grass_05"];
	this.cliffOuter1 = ["temp_dirt_gravel", "temp_dirt_gravel_b"];
	this.cliffSmall = ["temp_dirt_gravel", "temp_dirt_gravel_b"];
	this.cliffUpper = ["temperate_cliff_02_dover"];
	this.cliffLower = ["temperate_cliff_02_dover"];
	//this.forestFloor = ["alpine_forrestfloor", "alpine_forrestfloor_snow"];
	this.forestFloor = ["temp_forest_01_mod"];
	this.forestFloorBadland = this.forestFloor;
	this.textureMine = ["temp_dirt_08_mod"];
	this.textureMineBadland = ["temp_dirt_08_mod"];
	setSunColor(0.8, 0.8, 0.8);
	setSunElevation(0.5 * Math.PI);
	// setSkySet("overcast");
	setSkySet("stratus");
}

const TextureCliffTemp2 = function() {
	TextureCliff1.call(this);
	this.mine_stone = "gaia/rock/temperate_large";
	this.mine_metal = "gaia/ore/temperate_large";
	this.trees_inner = ["gaia/tree/temperate", "gaia/tree/pine", "gaia/tree/euro_birch"];
	// this.trees_badland = [];
	this.textureDefaultLow = ["steppe_grass_01", "steppe_grass_02", "steppe_grass_03"];
	this.textureDefaultHigh = ["steppe_grass_01", "steppe_grass_02", "steppe_grass_03", "steppe_grass_04", "steppe_grass_dirt_02", "steppe_grass_dirt_02", "steppe_grass_dirt_02"];
	// this.badlands1Inner = ["steppe_rocks_dirt_01", "steppe_rocks_dirt_01", "steppe_rocks_dirt_01", "steppe_grass_rocks_01"];
	this.badlands1Inner = ["steppe_dirt_a", "steppe_dirt_b", "steppe_dirt_rocks_a", "steppe_grass_dirt_33", "steppe_grass_dirt_66", "steppe_grass_dirt_33", "steppe_grass_dirt_66"];
	this.badlands1Outer = ["steppe_grass_dirt_33", "steppe_grass_dirt_66"];
	this.textureTown = ["aegean_paving_02"];
	this.textureTownOuter = ["steppe_grass_dirt_33", "steppe_grass_dirt_66", "aegean_paving_02"];
	this.cliffOuter2 = ["medit_grass_field_brown"];
	this.cliffOuter1 = ["medit_grass_wild_2"];
	this.cliffSmall = ["medit_cliff_italia_grass", "medit_grass_wild_2", "medit_grass_wild_2"];
	this.cliffUpper = ["medit_cliff_italia_grass", "medit_cliff_italia", "medit_cliff_italia"];
	this.cliffLower = ["medit_cliff_italia_grass", "medit_cliff_italia", "medit_cliff_italia"];
	this.forestFloor = ["steppe_grass_dirt_01", "steppe_grass_mud_01"];
	// this.forestFloorBadland = this.forestFloor;
	this.textureMine = ["steppe_grass_rocks_01"];
	this.textureMineBadland = ["steppe_grass_rocks_01"];
	setSunColor(0.8, 0.8, 0.8);
	setSunElevation(0.5 * Math.PI);
	// setSkySet("overcast");
	setSkySet("stratus");
}

/*************************************
 * Main
 *************************************/

// all the topo-creator
const tr1 = new TopoPie(1,1.2/numPlayers);
const tr2 = new TopoRings(23);
const tr3 = new TopoCenterHole(30,25);

// all the filters
const trc = new FilterCosContrast(1);
trc.addFun(tr1);

const tra = new FilterMul();
tra.addFun(trc);
tra.addFun(tr2);

const trb = new FilterContrast(0.9, 1.0);
trb.addFun(tr1);
const trd = new FilterDiv();
trd.addFun(tra);
trd.addFun(trb);
const tr = new FilterMin();
tr.addFun(tr3);
tr.addFun(trd);

// all the distorsion
const di1 = new DistortDiv(47, 0.4 + 0.4 * Math.random(), randomS(0.4) * Math.PI);
const di2 = new DistortDiv(37, 0.7 + 0.4 * Math.random(), randomS(0.3) * Math.PI);
const di3 = new DistortDiv(41, 0.3 + 0.3 * Math.random(), randomS(0.4) * Math.PI);
const di4 = new DistortDiv(19, 0.3 + 0.3 * Math.random(), randomS(0.2) * Math.PI);
tr1.addDistorsion(di1);
tr1.addDistorsion(di2);
tr.addDistorsion(di3);
tr.addDistorsion(di4);

const m = new Map(mapSize,numPlayers);

// add topofunction
m.shape(tr);

// create towns
const tcc = new TownCenterCircular(0);
m.setTownCenters(tcc);

// create mines
const mcc = new Mines();
m.setMines(mcc);

// create texture and items
// const texture1 = new TextureHeight();
// const texture1 = new TextureCliff1();
// const texture1 = new TextureCliffAlpin1();
// const texture1 = new TextureCliffAlpin2();
// const texture1 = new TextureCliffTemp1();
const texture1 = new TextureCliffTemp2();
// const texture1 = new TextureCliffPolar();

const badlands1 = new TopoDivide(20);
badlands1.addDistorsion(new DistortDiv(41 + 20 * Math.random(), 0.1, 0.8 * Math.PI));
const badlands1f = new FilterSub();
badlands1f.addFun(badlands1);
badlands1f.addFun(trc);

const badlands1Filter = new FilterContrast(0.2,1.0);
badlands1Filter.addFun(badlands1f);
texture1.setBadlands1(badlands1Filter);

const texture1forest = new TopoDivide(20);
const forest1f = new FilterMin();
forest1f.addFun(texture1forest);
forest1f.addFun(trc);
const forest1Filter = new FilterContrast(0.4,0.5);
forest1Filter.addFun(forest1f);
texture1.setForest(forest1Filter);
m.setTexture(texture1);

// create map
m.create();

g_Map.ExportMap();
