Source: rmgen-common/wall_builder.js

/**
 * @file Contains functionality to place walls on random maps.
 */

/**
 * Set some globals for this module.
 */
var g_WallStyles = loadWallsetsFromCivData();
var g_FortressTypes = createDefaultFortressTypes();

/**
 * Fetches wallsets from {civ}.json files, and then uses them to load
 * basic wall elements.
 */
function loadWallsetsFromCivData()
{
	let wallsets = {};
	for (let civ in g_CivData)
	{
		let civInfo = g_CivData[civ];
		if (!civInfo.WallSets)
			continue;

		for (let path of civInfo.WallSets)
		{
			// File naming conventions:
			// - structures/wallset_{style}
			// - structures/{civ}/wallset_{style}
			let style = basename(path).split("_")[1];
			if (path.split("/").indexOf(civ) != -1)
				style = civ + "/" + style;

			if (!wallsets[style])
				wallsets[style] = loadWallset(Engine.GetTemplate(path), civ);
		}
	}
	return wallsets;
}

function loadWallset(wallsetPath, civ)
{
	let newWallset = { "curves": [] };
	let wallsetData = GetTemplateDataHelper(wallsetPath).wallSet;

	for (let element in wallsetData.templates)
		if (element == "curves")
			for (let filename of wallsetData.templates.curves)
				newWallset.curves.push(readyWallElement(filename, civ));
		else
			newWallset[element] = readyWallElement(wallsetData.templates[element], civ);

	newWallset.overlap = wallsetData.minTowerOverlap * newWallset.tower.length;

	return newWallset;
}

/**
 * Fortress class definition
 *
 * We use "fortress" to describe a closed wall built of multiple wall
 * elements attached together surrounding a central point. We store the
 * abstract of the wall (gate, tower, wall, ...) and only apply the style
 * when we get to build it.
 *
 * @param {string} type - Descriptive string, example: "tiny". Not really needed (WallTool.wallTypes["type string"] is used). Mainly for custom wall elements.
 * @param {array} [wall] - Array of wall element strings. May be defined at a later point.
 *                         Example: ["medium", "cornerIn", "gate", "cornerIn", "medium", "cornerIn", "gate", "cornerIn"]
 * @param {Object} [centerToFirstElement] - Vector from the visual center of the fortress to the first wall element.
 * @param {number} [centerToFirstElement.x]
 * @param {number} [centerToFirstElement.y]
 */
function Fortress(type, wall=[], centerToFirstElement=undefined)
{
	this.type = type;
	this.wall = wall;
	this.centerToFirstElement = centerToFirstElement;
}

function createDefaultFortressTypes()
{
	let defaultFortresses = {};

	/**
	 * Define some basic default fortress types.
	 */
	let addFortress = (type, walls) => defaultFortresses[type] = { "wall": walls.concat(walls, walls, walls) };
	addFortress("tiny", ["gate", "tower", "short", "cornerIn", "short", "tower"]);
	addFortress("small", ["gate", "tower", "medium", "cornerIn", "medium", "tower"]);
	addFortress("medium", ["gate", "tower", "long", "cornerIn", "long", "tower"]);
	addFortress("normal", ["gate", "tower", "medium", "cornerIn", "medium", "cornerOut", "medium", "cornerIn", "medium", "tower"]);
	addFortress("large", ["gate", "tower", "long", "cornerIn", "long", "cornerOut", "long", "cornerIn", "long", "tower"]);
	addFortress("veryLarge", ["gate", "tower", "medium", "cornerIn", "medium", "cornerOut", "long", "cornerIn", "long", "cornerOut", "medium", "cornerIn", "medium", "tower"]);
	addFortress("giant", ["gate", "tower", "long", "cornerIn", "long", "cornerOut", "long", "cornerIn", "long", "cornerOut", "long", "cornerIn", "long", "tower"]);

	/**
	 * Define some fortresses based on those above, but designed for use
	 * with the "palisades" wallset.
	 */
	for (let fortressType in defaultFortresses)
	{
		const fillTowersBetween = ["short", "medium", "long", "start", "end", "cornerIn", "cornerOut"];
		const newKey = fortressType + "Palisades";
		const oldWall = defaultFortresses[fortressType].wall;

		defaultFortresses[newKey] = { "wall": [] };
		for (let j = 0; j < oldWall.length; ++j)
		{
			defaultFortresses[newKey].wall.push(oldWall[j]);

			if (j + 1 < oldWall.length &&
				fillTowersBetween.indexOf(oldWall[j]) != -1 &&
				fillTowersBetween.indexOf(oldWall[j + 1]) != -1)
			{
				defaultFortresses[newKey].wall.push("tower");
			}
		}
	}

	return defaultFortresses;
}

/**
 * Define some helper functions
 */

/**
 * Get a wall element of a style.
 *
 * Valid elements:
 *   long, medium, short, start, end, cornerIn, cornerOut, tower, fort, gate, entry, entryTower, entryFort
 *
 * Dynamic elements:
 *   `gap_{x}` returns a non-blocking gap of length `x` meters.
 *   `turn_{x}` returns a zero-length turn of angle `x` radians.
 *
 * Any other arbitrary string passed will be attempted to be used as: `structures/{civ}/{arbitrary_string}`.
 *
 * @param {string} element - What sort of element to fetch.
 * @param {string} [style] - The style from which this element should come from.
 * @returns {Object} The wall element requested. Or a tower element.
 */
function getWallElement(element, style)
{
	style = validateStyle(style);
	if (g_WallStyles[style][element])
		return g_WallStyles[style][element];

	// Attempt to derive any unknown elements.
	// Defaults to a wall tower piece
	const quarterBend = Math.PI / 2;
	let wallset = g_WallStyles[style];
	let civ = style.split("/")[0];
	let ret = wallset.tower ? clone(wallset.tower) : { "angle": 0, "bend": 0, "length": 0, "indent": 0 };

	switch (element)
	{
	case "cornerIn":
		if (wallset.curves)
			for (let curve of wallset.curves)
				if (curve.bend == quarterBend)
					ret = curve;

		if (ret.bend != quarterBend)
		{
			ret.angle += Math.PI / 4;
			ret.indent = ret.length / 4;
			ret.length = 0;
			ret.bend = Math.PI / 2;
		}
		break;

	case "cornerOut":
		if (wallset.curves)
			for (let curve of wallset.curves)
				if (curve.bend == quarterBend)
				{
					ret = clone(curve);
					ret.angle += Math.PI / 2;
					ret.indent -= ret.indent * 2;
				}

		if (ret.bend != quarterBend)
		{
			ret.angle -= Math.PI / 4;
			ret.indent = -ret.length / 4;
			ret.length = 0;
		}
		ret.bend = -Math.PI / 2;
		break;

	case "entry":
		ret.templateName = undefined;
		ret.length = wallset.gate.length;
		break;

	case "entryTower":
		ret.templateName = g_CivData[civ] ? "structures/" + civ + "/defense_tower" : "structures/palisades_watchtower";
		ret.indent = ret.length * -3;
		ret.length = wallset.gate.length;
		break;

	case "entryFort":
		ret = clone(wallset.fort);
		ret.angle -= Math.PI;
		ret.length *= 1.5;
		ret.indent = ret.length;
		break;

	case "start":
		if (wallset.end)
		{
			ret = clone(wallset.end);
			ret.angle += Math.PI;
		}
		break;

	case "end":
		if (wallset.end)
			ret = wallset.end;
		break;

	default:
		if (element.startsWith("gap_"))
		{
			ret.templateName = undefined;
			ret.angle = 0;
			ret.length = +element.slice("gap_".length);
		}
		else if (element.startsWith("turn_"))
		{
			ret.templateName = undefined;
			ret.bend = +element.slice("turn_".length) * Math.PI;
			ret.length = 0;
		}
		else
		{
			if (!g_CivData[civ])
				civ = Object.keys(g_CivData)[0];

			let templateName = "structures/" + civ + "/" + element;
			if (Engine.TemplateExists(templateName))
			{
				ret.indent = ret.length * (element == "outpost" || element.endsWith("_tower") ? -3 : 3.5);
				ret.templateName = templateName;
				ret.length = 0;
			}
			else
				warn("Unrecognised wall element: '" + element + "' (" + style + "). Defaulting to " + (wallset.tower ? "'tower'." : "a blank element."));
		}
	}

	// Cache to save having to calculate this element again.
	g_WallStyles[style][element] = deepfreeze(ret);

	return ret;
}

/**
 * Prepare a wall element for inclusion in a style.
 *
 * @param {string} path - The template path to read values from
 */
function readyWallElement(path, civCode)
{
	path = path.replace(/\{civ\}/g, civCode);
	let template = GetTemplateDataHelper(Engine.GetTemplate(path), null, null);
	let length = template.wallPiece ? template.wallPiece.length : template.obstruction.shape.width;

	return deepfreeze({
		"templateName": path,
		"angle": template.wallPiece ? template.wallPiece.angle : Math.PI,
		"length": length / TERRAIN_TILE_SIZE,
		"indent": template.wallPiece ? template.wallPiece.indent / TERRAIN_TILE_SIZE : 0,
		"bend": template.wallPiece ? template.wallPiece.bend : 0
	});
}

/**
 * Returns a list of objects containing all information to place all the wall elements entities with placeObject (but the player ID)
 * Placing the first wall element at startX/startY placed with an angle given by orientation
 * An alignment can be used to get the "center" of a "wall" (more likely used for fortresses) with getCenterToFirstElement
 *
 * @param {Vector2D} position
 * @param {array} [wall]
 * @param {string} [style]
 * @param {number} [orientation]
 * @returns {array}
 */
function getWallAlignment(position, wall = [], style = "athen_stone", orientation = 0)
{
	style = validateStyle(style);
	let alignment = [];
	let wallPosition = position.clone();

	for (let i = 0; i < wall.length; ++i)
	{
		let element = getWallElement(wall[i], style);
		if (!element && i == 0)
		{
			warn("Not a valid wall element: style = " + style + ", wall[" + i + "] = " + wall[i] + "; " + uneval(element));
			continue;
		}

		// Add wall elements entity placement arguments to the alignment
		alignment.push({
			"position": Vector2D.sub(wallPosition, new Vector2D(element.indent, 0).rotate(-orientation)),
			"templateName": element.templateName,
			"angle": orientation + element.angle
		});

		// Preset vars for the next wall element
		if (i + 1 < wall.length)
		{
			orientation += element.bend;
			let nextElement = getWallElement(wall[i + 1], style);
			if (!nextElement)
			{
				warn("Not a valid wall element: style = " + style + ", wall[" + (i + 1) + "] = " + wall[i + 1] + "; " + uneval(nextElement));
				continue;
			}

			let distance = (element.length + nextElement.length) / 2 - g_WallStyles[style].overlap;

			// Corrections for elements with indent AND bending
			let indent = element.indent;
			let bend = element.bend;
			if (bend != 0 && indent != 0)
			{
				// Indent correction to adjust distance
				distance += indent * Math.sin(bend);

				// Indent correction to normalize indentation
				wallPosition.add(new Vector2D(indent).rotate(-orientation));
			}

			// Set the next coordinates of the next element in the wall without indentation adjustment
			wallPosition.add(new Vector2D(distance, 0).rotate(-orientation).perpendicular());
		}
	}
	return alignment;
}

/**
 * Center calculation works like getting the center of mass assuming all wall elements have the same "weight"
 *
 * Used to get centerToFirstElement of fortresses by default
 *
 * @param {number} alignment
 * @returns {Object} Vector from the center of the set of aligned wallpieces to the first wall element.
 */
function getCenterToFirstElement(alignment)
{
	return alignment.reduce((result, align) => result.sub(Vector2D.div(align.position, alignment.length)), new Vector2D(0, 0));
}

/**
 * Does not support bending wall elements like corners.
 *
 * @param {string} style
 * @param {array} wall
 * @returns {number} The sum length (in terrain cells, not meters) of the provided wall.
 */
function getWallLength(style, wall)
{
	style = validateStyle(style);

	let length = 0;
	let overlap = g_WallStyles[style].overlap;
	for (let element of wall)
		length += getWallElement(element, style).length - overlap;

	return length;
}

/**
 * Makes sure the style exists and, if not, provides a fallback.
 *
 * @param {string} style
 * @param {number} [playerId]
 * @returns {string} Valid style.
 */
function validateStyle(style, playerId = 0)
{
	if (!style || !g_WallStyles[style])
	{
		if (playerId == 0)
			return Object.keys(g_WallStyles)[0];

		style = getCivCode(playerId) + "/stone";
		return !g_WallStyles[style] ? Object.keys(g_WallStyles)[0] : style;
	}
	return style;
}

/**
 * Define the different wall placer functions
 */

/**
 * Places an abitrary wall beginning at the location comprised of the array of elements provided.
 *
 * @param {Vector2D} position
 * @param {array} [wall] - Array of wall element types. Example: ["start", "long", "tower", "long", "end"]
 * @param {string} [style] - Wall style string.
 * @param {number} [playerId] - Identifier of the player for whom the wall will be placed.
 * @param {number} [orientation] - Angle at which the first wall element is placed.
 *                                 0 means "outside" or "front" of the wall is right (positive X) like placeObject
 *                                 It will then be build towards top/positive Y (if no bending wall elements like corners are used)
 *                                 Raising orientation means the wall is rotated counter-clockwise like placeObject
 */
function placeWall(position, wall = [], style, playerId = 0, orientation = 0, constraints = undefined)
{
	style = validateStyle(style, playerId);

	let entities = [];
	let constraint = new StaticConstraint(constraints);

	for (let align of getWallAlignment(position, wall, style, orientation))
		if (align.templateName && g_Map.inMapBounds(align.position) && constraint.allows(align.position.clone().floor()))
			entities.push(g_Map.placeEntityPassable(align.templateName, playerId, align.position, align.angle));

	return entities;
}

/**
 * Places an abitrarily designed "fortress" (closed loop of wall elements)
 * centered around a given point.
 *
 * The fortress wall should always start with the main entrance (like
 * "entry" or "gate") to get the orientation correct.
 *
 * @param {Vector2D} centerPosition
 * @param {Object} [fortress] - If not provided, defaults to the predefined "medium" fortress type.
 * @param {string} [style] - Wall style string.
 * @param {number} [playerId] - Identifier of the player for whom the wall will be placed.
 * @param {number} [orientation] - Angle the first wall element (should be a gate or entrance) is placed. Default is 0
 */
function placeCustomFortress(centerPosition, fortress, style, playerId = 0, orientation = 0, constraints = undefined)
{
	fortress = fortress || g_FortressTypes.medium;
	style = validateStyle(style, playerId);

	// Calculate center if fortress.centerToFirstElement is undefined (default)
	let centerToFirstElement = fortress.centerToFirstElement;
	if (centerToFirstElement === undefined)
		centerToFirstElement = getCenterToFirstElement(getWallAlignment(new Vector2D(0, 0), fortress.wall, style));

	// Placing the fortress wall
	let position = Vector2D.sum([
		centerPosition,
		new Vector2D(centerToFirstElement.x, 0).rotate(-orientation),
		new Vector2D(centerToFirstElement.y, 0).perpendicular().rotate(-orientation)
	]);

	return placeWall(position, fortress.wall, style, playerId, orientation, constraints);
}

/**
 * Places a predefined fortress centered around the provided point.
 *
 * @see Fortress
 *
 * @param {string} [type] - Predefined fortress type, as used as a key in g_FortressTypes.
 */
function placeFortress(centerPosition, type = "medium", style, playerId = 0, orientation = 0, constraints = undefined)
{
	return placeCustomFortress(centerPosition, g_FortressTypes[type], style, playerId, orientation, constraints);
}

/**
 * Places a straight wall from a given point to another, using the provided
 * wall parts repeatedly.
 *
 * Note: Any "bending" wall pieces passed will be complained about.
 *
 * @param {Vector2D} startPosition - Approximate start point of the wall.
 * @param {Vector2D} targetPosition - Approximate end point of the wall.
 * @param {array} [wallPart=["tower", "long"]]
 * @param {number} [playerId]
 * @param {boolean} [endWithFirst] - If true, the first wall element will also be the last.
 */
function placeLinearWall(startPosition, targetPosition, wallPart = undefined, style, playerId = 0, endWithFirst = true, constraints = undefined)
{
	wallPart = wallPart || ["tower", "long"];
	style = validateStyle(style, playerId);

	// Check arguments
	for (let element of wallPart)
		if (getWallElement(element, style).bend != 0)
			warn("placeLinearWall : Bending is not supported by this function, but the following bending wall element was used: " + element);

	// Setup number of wall parts
	let totalLength = startPosition.distanceTo(targetPosition);
	let wallPartLength = getWallLength(style, wallPart);
	let numParts = Math.ceil(totalLength / wallPartLength);
	if (endWithFirst)
		numParts = Math.ceil((totalLength - getWallElement(wallPart[0], style).length) / wallPartLength);

	// Setup scale factor
	let scaleFactor = totalLength / (numParts * wallPartLength);
	if (endWithFirst)
		scaleFactor = totalLength / (numParts * wallPartLength + getWallElement(wallPart[0], style).length);

	// Setup angle
	let wallAngle = getAngle(startPosition.x, startPosition.y, targetPosition.x, targetPosition.y);
	let placeAngle = wallAngle - Math.PI / 2;

	// Place wall entities
	let entities = [];
	let position = startPosition.clone();
	let overlap = g_WallStyles[style].overlap;
	let constraint = new StaticConstraint(constraints);
	for (let partIndex = 0; partIndex < numParts; ++partIndex)
		for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex)
		{
			let wallEle = getWallElement(wallPart[elementIndex], style);

			let wallLength = (wallEle.length - overlap) / 2;
			let dist = new Vector2D(scaleFactor * wallLength, 0).rotate(-wallAngle);

			// Length correction
			position.add(dist);

			// Indent correction
			let place = Vector2D.add(position, new Vector2D(0, wallEle.indent).rotate(-wallAngle));

			if (wallEle.templateName && g_Map.inMapBounds(place) && constraint.allows(place.clone().floor()))
				entities.push(g_Map.placeEntityPassable(wallEle.templateName, playerId, place, placeAngle + wallEle.angle));

			position.add(dist);
		}

	if (endWithFirst)
	{
		let wallEle = getWallElement(wallPart[0], style);
		let wallLength = (wallEle.length - overlap) / 2;
		position.add(new Vector2D(scaleFactor * wallLength, 0).rotate(-wallAngle));
		if (wallEle.templateName && g_Map.inMapBounds(position) && constraint.allows(position.clone().floor()))
			entities.push(g_Map.placeEntityPassable(wallEle.templateName, playerId, position, placeAngle + wallEle.angle));
	}

	return entities;
}

/**
 * Places a (semi-)circular wall of repeated wall elements around a central
 * point at a given radius.
 *
 * The wall does not have to be closed, and can be left open in the form
 * of an arc if maxAngle < 2 * Pi. In this case, the orientation determines
 * where this open part faces, with 0 meaning "right" like an unrotated
 * building's drop-point.
 *
 * Note: Any "bending" wall pieces passed will be complained about.
 *
 * @param {Vector2D} center - Center of the circle or arc.
 * @param (number} radius - Approximate radius of the circle. (Given the maxBendOff argument)
 * @param {array} [wallPart]
 * @param {string} [style]
 * @param {number} [playerId]
 * @param {number} [orientation] - Angle at which the first wall element is placed.
 * @param {number} [maxAngle] - How far the wall should circumscribe the center. Default is Pi * 2 (for a full circle).
 * @param {boolean} [endWithFirst] - If true, the first wall element will also be the last. For full circles, the default is false. For arcs, true.
 * @param {number} [maxBendOff]    Optional. How irregular the circle should be. 0 means regular circle, PI/2 means very irregular. Default is 0 (regular circle)
 */
function placeCircularWall(center, radius, wallPart, style, playerId = 0, orientation = 0, maxAngle = 2 * Math.PI, endWithFirst, maxBendOff = 0, constraints = undefined)
{
	wallPart = wallPart || ["tower", "long"];
	style = validateStyle(style, playerId);

	if (endWithFirst === undefined)
		endWithFirst = maxAngle < Math.PI * 2 - 0.001; // Can this be done better?

	// Check arguments
	if (maxBendOff > Math.PI / 2 || maxBendOff < 0)
		warn("placeCircularWall : maxBendOff should satisfy 0 < maxBendOff < PI/2 (~1.5rad) but it is: " + maxBendOff);

	for (let element of wallPart)
		if (getWallElement(element, style).bend != 0)
			warn("placeCircularWall : Bending is not supported by this function, but the following bending wall element was used: " + element);

	// Setup number of wall parts
	let totalLength = maxAngle * radius;
	let wallPartLength = getWallLength(style, wallPart);
	let numParts = Math.ceil(totalLength / wallPartLength);
	if (endWithFirst)
		numParts = Math.ceil((totalLength - getWallElement(wallPart[0], style).length) / wallPartLength);

	// Setup scale factor
	let scaleFactor = totalLength / (numParts * wallPartLength);
	if (endWithFirst)
		scaleFactor = totalLength / (numParts * wallPartLength + getWallElement(wallPart[0], style).length);

	// Place wall entities
	let entities = [];
	let constraint = new StaticConstraint(constraints);
	let actualAngle = orientation;
	let position = Vector2D.add(center, new Vector2D(radius, 0).rotate(-actualAngle));
	let overlap = g_WallStyles[style].overlap;
	for (let partIndex = 0; partIndex < numParts; ++partIndex)
		for (let wallEle of wallPart)
		{
			wallEle = getWallElement(wallEle, style);

			// Width correction
			let addAngle = scaleFactor * (wallEle.length - overlap) / radius;
			let target = Vector2D.add(center, new Vector2D(radius, 0).rotate(-actualAngle - addAngle));
			let place = Vector2D.average([position, target]);
			let placeAngle = actualAngle + addAngle / 2;

			// Indent correction
			place.sub(new Vector2D(wallEle.indent, 0).rotate(-placeAngle));

			// Placement
			if (wallEle.templateName && g_Map.inMapBounds(place) && constraint.allows(place.clone().floor()))
			{
				let entity = g_Map.placeEntityPassable(wallEle.templateName, playerId, place, placeAngle + wallEle.angle);
				if (entity)
					entities.push(entity);
			}

			// Prepare for the next wall element
			actualAngle += addAngle;
			position = Vector2D.add(center, new Vector2D(radius, 0).rotate(-actualAngle));
		}

	if (endWithFirst)
	{
		let wallEle = getWallElement(wallPart[0], style);
		let addAngle = scaleFactor * wallEle.length / radius;
		let target = Vector2D.add(center, new Vector2D(radius, 0).rotate(-actualAngle - addAngle));
		let place = Vector2D.average([position, target]);
		let placeAngle = actualAngle + addAngle / 2;
		if (g_Map.inMapBounds(place) && constraint.allows(place.clone().floor()))
			entities.push(g_Map.placeEntityPassable(wallEle.templateName, playerId, place, placeAngle + wallEle.angle));
	}

	return entities;
}

/**
 * Places a polygonal wall of repeated wall elements around a central
 * point at a given radius.
 *
 * Note: Any "bending" wall pieces passed will be ignored.
 *
 * @param {Vector2D} centerPosition
 * @param {number} radius
 * @param {array} [wallPart]
 * @param {string} [cornerWallElement] - Wall element to be placed at the polygon's corners.
 * @param {string} [style]
 * @param {number} [playerId]
 * @param {number} [orientation] - Direction the first wall piece or opening in the wall faces.
 * @param {number} [numCorners] - How many corners the polygon will have.
 * @param {boolean} [skipFirstWall] - If the first linear wall part will be left opened as entrance.
 */
function placePolygonalWall(centerPosition, radius, wallPart, cornerWallElement = "tower", style, playerId = 0, orientation = 0, numCorners = 8, skipFirstWall = true, constraints = undefined)
{
	wallPart = wallPart || ["long", "tower"];
	style = validateStyle(style, playerId);

	let entities = [];
	let constraint = new StaticConstraint(constraints);
	let angleAdd = Math.PI * 2 / numCorners;
	let angleStart = orientation - angleAdd / 2;
	let corners = new Array(numCorners).fill(0).map((zero, i) =>
		Vector2D.add(centerPosition, new Vector2D(radius, 0).rotate(-angleStart - i * angleAdd)));

	for (let i = 0; i < numCorners; ++i)
	{
		let angleToCorner = getAngle(corners[i].x, corners[i].y, centerPosition.x, centerPosition.y);
		if (g_Map.inMapBounds(corners[i]) && constraint.allows(corners[i].clone().floor()))
		{
			let entity = g_Map.placeEntityPassable(getWallElement(cornerWallElement, style).templateName, playerId, corners[i], angleToCorner);
			if (entity)
				entities.push(entity);
		}

		if (!skipFirstWall || i != 0)
		{
			let cornerLength = getWallElement(cornerWallElement, style).length / 2;
			let cornerAngle = angleToCorner + angleAdd / 2;
			let targetCorner = (i + 1) % numCorners;
			let cornerPosition = new Vector2D(cornerLength, 0).rotate(-cornerAngle).perpendicular();

			entities = entities.concat(
				placeLinearWall(
					// Adjustment to the corner element width (approximately)
					Vector2D.sub(corners[i], cornerPosition),
					Vector2D.add(corners[targetCorner], cornerPosition),
					wallPart,
					style,
					playerId,
					undefined,
					constraints));
		}
	}

	return entities;
}

/**
 * Places an irregular polygonal wall consisting of parts semi-randomly
 * chosen from a provided assortment, built around a central point at a
 * given radius.
 *
 * Note: Any "bending" wall pieces passed will be ... I'm not sure. TODO: test what happens!
 *
 * Note: The wallPartsAssortment is last because it's the hardest to set.
 *
 * @param {Vector2D} centerPosition
 * @param {number} radius
 * @param {string} [cornerWallElement] - Wall element to be placed at the polygon's corners.
 * @param {string} [style]
 * @param {number} [playerId]
 * @param {number} [orientation] - Direction the first wallpiece or opening in the wall faces.
 * @param {number} [numCorners] - How many corners the polygon will have.
 * @param {number} [irregularity] - How irregular the polygon will be. 0 = regular, 1 = VERY irregular.
 * @param {boolean} [skipFirstWall] - If true, the first linear wall part will be left open as an entrance.
 * @param {array} [wallPartsAssortment] - An array of wall part arrays to choose from for each linear wall connecting the corners.
 */
function placeIrregularPolygonalWall(centerPosition, radius, cornerWallElement = "tower", style, playerId = 0, orientation = 0, numCorners, irregularity = 0.5, skipFirstWall = false, wallPartsAssortment = undefined, constraints = undefined)
{
	style = validateStyle(style, playerId);
	numCorners = numCorners || randIntInclusive(5, 7);

	// Generating a generic wall part assortment with each wall part including 1 gate lengthened by walls and towers
	// NOTE: It might be a good idea to write an own function for that...
	let defaultWallPartsAssortment = [["short"], ["medium"], ["long"], ["gate", "tower", "short"]];
	let centeredWallPart = ["gate"];
	let extendingWallPartAssortment = [["tower", "long"], ["tower", "medium"]];
	defaultWallPartsAssortment.push(centeredWallPart);
	for (let assortment of extendingWallPartAssortment)
	{
		let wallPart = centeredWallPart;
		for (let j = 0; j < radius; ++j)
		{
			if (j % 2 == 0)
				wallPart = wallPart.concat(assortment);
			else
			{
				assortment.reverse();
				wallPart = assortment.concat(wallPart);
				assortment.reverse();
			}
			defaultWallPartsAssortment.push(wallPart);
		}
	}

	// Setup optional arguments to the default
	wallPartsAssortment = wallPartsAssortment || defaultWallPartsAssortment;

	// Setup angles
	let angleToCover = Math.PI * 2;
	let angleAddList = [];
	for (let i = 0; i < numCorners; ++i)
	{
		// Randomize covered angles. Variety scales down with raising angle though...
		angleAddList.push(angleToCover / (numCorners - i) * (1 + randFloat(-irregularity, irregularity)));
		angleToCover -= angleAddList[angleAddList.length - 1];
	}

	// Setup corners
	let corners = [];
	let angleActual = orientation - angleAddList[0] / 2;
	for (let i = 0; i < numCorners; ++i)
	{
		corners.push(Vector2D.add(centerPosition, new Vector2D(radius, 0).rotate(-angleActual)));

		if (i < numCorners - 1)
			angleActual += angleAddList[i + 1];
	}

	// Setup best wall parts for the different walls (a bit confusing naming...)
	let wallPartLengths = [];
	let maxWallPartLength = 0;
	for (let wallPart of wallPartsAssortment)
	{
		let length = getWallLength(style, wallPart);
		wallPartLengths.push(length);
		if (length > maxWallPartLength)
			maxWallPartLength = length;
	}

	let wallPartList = []; // This is the list of the wall parts to use for the walls between the corners, not to confuse with wallPartsAssortment!
	for (let i = 0; i < numCorners; ++i)
	{
		let bestWallPart = []; // This is a simple wall part not a wallPartsAssortment!
		let bestWallLength = Infinity;
		let targetCorner = (i + 1) % numCorners;
		// NOTE: This is not quite the length the wall will be in the end. Has to be tweaked...
		let wallLength = corners[i].distanceTo(corners[targetCorner]);
		let numWallParts = Math.ceil(wallLength / maxWallPartLength);
		for (let partIndex = 0; partIndex < wallPartsAssortment.length; ++partIndex)
		{
			let linearWallLength = numWallParts * wallPartLengths[partIndex];
			if (linearWallLength < bestWallLength && linearWallLength > wallLength)
			{
				bestWallPart = wallPartsAssortment[partIndex];
				bestWallLength = linearWallLength;
			}
		}
		wallPartList.push(bestWallPart);
	}

	// Place Corners and walls
	let entities = [];
	let constraint = new StaticConstraint(constraints);
	for (let i = 0; i < numCorners; ++i)
	{
		let angleToCorner = getAngle(corners[i].x, corners[i].y, centerPosition.x, centerPosition.y);
		if (g_Map.inMapBounds(corners[i]) && constraint.allows(corners[i].clone().floor()))
			entities.push(
				g_Map.placeEntityPassable(getWallElement(cornerWallElement, style).templateName, playerId, corners[i], angleToCorner));

		if (!skipFirstWall || i != 0)
		{
			let cornerLength = getWallElement(cornerWallElement, style).length / 2;
			let targetCorner = (i + 1) % numCorners;
			let startAngle = angleToCorner + angleAddList[i] / 2;
			let targetAngle = angleToCorner + angleAddList[targetCorner] / 2;

			entities = entities.concat(
				placeLinearWall(
					// Adjustment to the corner element width (approximately)
					Vector2D.sub(corners[i], new Vector2D(cornerLength, 0).perpendicular().rotate(-startAngle)),
					Vector2D.add(corners[targetCorner], new Vector2D(cornerLength, 0).rotate(-targetAngle - Math.PI / 2)),
					wallPartList[i],
					style,
					playerId,
					false,
					constraints));
		}
	}
	return entities;
}

/**
 * Places a generic fortress with towers at the edges connected with long
 * walls and gates, positioned around a central point at a given radius.
 *
 * The difference between this and the other two Fortress placement functions
 * is that those place a predefined fortress, regardless of terrain type.
 * This function attempts to intelligently place a wall circuit around
 * the central point taking into account terrain and other obstacles.
 *
 * This is the default Iberian civ bonus starting wall.
 *
 * @param {Vector2D} center - The approximate center coordinates of the fortress
 * @param {number} [radius] - The approximate radius of the wall to be placed.
 * @param {number} [playerId]
 * @param {string} [style]
 * @param {number} [irregularity] - 0 = circle, 1 = very spiky
 * @param {number} [gateOccurence] - Integer number, every n-th walls will be a gate instead.
 * @param {number} [maxTries] - How often the function tries to find a better fitting shape.
 */
function placeGenericFortress(center, radius = 20, playerId = 0, style, irregularity = 0.5, gateOccurence = 3, maxTries = 100, constraints = undefined)
{
	style = validateStyle(style, playerId);

	// Setup some vars
	let startAngle = randomAngle();
	let actualOff = new Vector2D(radius, 0).rotate(-startAngle);
	let actualAngle = startAngle;
	let pointDistance = getWallLength(style, ["long", "tower"]);

	// Searching for a well fitting point derivation
	let tries = 0;
	let bestPointDerivation;
	let minOverlap = 1000;
	let overlap;
	while (tries < maxTries && minOverlap > g_WallStyles[style].overlap)
	{
		let pointDerivation = [];
		let distanceToTarget = 1000;
		while (true)
		{
			let indent = randFloat(-irregularity * pointDistance, irregularity * pointDistance);
			let tmp = new Vector2D(radius + indent, 0).rotate(-actualAngle - pointDistance / radius);
			let tmpAngle = getAngle(actualOff.x, actualOff.y, tmp.x, tmp.y);

			actualOff.add(new Vector2D(pointDistance, 0).rotate(-tmpAngle));
			actualAngle = getAngle(0, 0, actualOff.x, actualOff.y);
			pointDerivation.push(actualOff.clone());
			distanceToTarget = pointDerivation[0].distanceTo(actualOff);

			let numPoints = pointDerivation.length;
			if (numPoints > 3 && distanceToTarget < pointDistance) // Could be done better...
			{
				overlap = pointDistance - pointDerivation[numPoints - 1].distanceTo(pointDerivation[0]);
				if (overlap < minOverlap)
				{
					minOverlap = overlap;
					bestPointDerivation = pointDerivation;
				}
				break;
			}
		}
		++tries;
	}

	log("placeGenericFortress: Reduced overlap to " + minOverlap + " after " + tries + " tries");

	// Place wall
	let entities = [];
	let constraint = new StaticConstraint(constraints);
	for (let pointIndex = 0; pointIndex < bestPointDerivation.length; ++pointIndex)
	{
		let start = Vector2D.add(center, bestPointDerivation[pointIndex]);
		let target = Vector2D.add(center, bestPointDerivation[(pointIndex + 1) % bestPointDerivation.length]);
		let angle = getAngle(start.x, start.y, target.x, target.y);

		let element = (pointIndex + 1) % gateOccurence == 0 ? "gate" : "long";
		element = getWallElement(element, style);

		if (element.templateName)
		{
			let pos = Vector2D.add(start, new Vector2D(start.distanceTo(target) / 2, 0).rotate(-angle));
			if (g_Map.inMapBounds(pos) && constraint.allows(pos.clone().floor()))
				entities.push(g_Map.placeEntityPassable(element.templateName, playerId, pos, angle - Math.PI / 2 + element.angle));
		}

		// Place tower
		start = Vector2D.add(center, bestPointDerivation[(pointIndex + bestPointDerivation.length - 1) % bestPointDerivation.length]);
		angle = getAngle(start.x, start.y, target.x, target.y);

		let tower = getWallElement("tower", style);
		let pos = Vector2D.add(center, bestPointDerivation[pointIndex]);
		if (g_Map.inMapBounds(pos) && constraint.allows(pos.clone().floor()))
			entities.push(
				g_Map.placeEntityPassable(tower.templateName, playerId, pos, angle - Math.PI / 2 + tower.angle));
	}

	return entities;
}