/**
* A Centered Placer generates a shape (array of Vector2D points) around a variable center location satisfying a Constraint.
* The center can be modified externally using setCenterPosition, typically called by createAreas.
*/
Engine.LoadLibrary("rmgen/placer/centered");
/**
* A Non-Centered Placer generates a shape (array of Vector2D points) at a fixed location meeting a Constraint and
* is typically called by createArea.
* Since this type of Placer has no x and z property, its location cannot be randomized using createAreas.
*/
Engine.LoadLibrary("rmgen/placer/noncentered");
/**
* A Painter modifies an arbitrary feature in a given Area, for instance terrain textures, elevation or calling other painters on that Area.
* Typically the area is determined by a Placer called from createArea or createAreas.
*/
Engine.LoadLibrary("rmgen/painter");
const TERRAIN_SEPARATOR = "|";
const SEA_LEVEL = 20.0;
const HEIGHT_UNITS_PER_METRE = 92;
/**
* Constants needed for heightmap_manipulation.js
*/
const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE; // Engine limit, Roughly 700 meters
const MIN_HEIGHT = - SEA_LEVEL;
const MAX_HEIGHT = MAX_HEIGHT_RANGE - SEA_LEVEL;
/**
* Default angle for buildings.
*/
const BUILDING_ORIENTATION = -1/4 * Math.PI;
const g_CivData = deepfreeze(loadCivFiles(false));
const g_ActorPrefix = "actor|";
/**
* Sets whether setHeight operates on the center of a tile or on the vertices.
*/
var TILE_CENTERED_HEIGHT_MAP = false;
function actorTemplate(templateName)
{
return g_ActorPrefix + templateName + ".xml";
}
function getObstructionSize(templateName, margin = 0)
{
let obstruction = Engine.GetTemplate(templateName).Obstruction;
let obstructionSize =
obstruction.Static ?
new Vector2D(obstruction.Static["@depth"], obstruction.Static["@width"]) :
// Used for gates, should consider the position too
obstruction.Obstructions ?
new Vector2D(
Object.keys(obstruction.Obstructions).reduce((depth, key) => Math.max(depth, +obstruction.Obstructions[key]["@depth"]), 0),
Object.keys(obstruction.Obstructions).reduce((width, key) => width + +obstruction.Obstructions[key]["@width"], 0)) :
new Vector2D(0, 0);
return obstructionSize.div(TERRAIN_TILE_SIZE).add(new Vector2D(2, 2).mult(margin));
}
function fractionToTiles(f)
{
return g_MapSettings.Size * f;
}
function tilesToFraction(t)
{
return t / g_MapSettings.Size;
}
function scaleByMapSize(min, max, minMapSize = 128, maxMapSize = 512)
{
return min + (max - min) * (g_MapSettings.Size - minMapSize) / (maxMapSize - minMapSize);
}
/**
* Interpolate quadratic between (min, minMapArea) and (max, maxMapArea) with respect to the mapSize.
* Default values set on the area of tiny and giant map sizes according to the map shape (square or circular).
*/
function scaleByMapArea(min, max, minMapArea = g_Map.getArea(128), maxMapArea = g_Map.getArea(256))
{
return min + (max - min) * (g_Map.getArea() - minMapArea) / (maxMapArea - minMapArea);
}
/**
* Interpolate quadraticly between (0,0) and (base, baseArea) with respect to the map size.
* @param base - Value we should attain at baseArea.
* @param disallowedArea - Area deducted from the map area.
* @param baseArea - Area at which the base value should be attained. Defaults to the map area of a tiny map of current shape (square or circular).
*/
function scaleByMapAreaAbsolute(base, disallowedArea = 0, baseArea = g_Map.getArea(128))
{
return scaleByMapArea(0, base, disallowedArea, baseArea + disallowedArea);
}
function randomPositionOnTile(tilePosition)
{
return Vector2D.add(tilePosition, new Vector2D(randFloat(0, 1), randFloat(0, 1)));
}
/**
* Retries the given function with those arguments as often as specified.
*/
function retryPlacing(placeFunc, retryFactor, amount, behaveDeprecated = false)
{
let maxFail = amount * retryFactor;
let results = [];
let bad = 0;
while (results.length < amount && bad <= maxFail)
{
let result = placeFunc();
if (result !== undefined || behaveDeprecated)
results.push(result);
else
++bad;
}
return results;
}
// TODO this is a hack to simulate the old behaviour of those functions
// until all old maps are changed to use the correct version of these functions
function createObjectGroupsDeprecated(group, player, constraints, amount, retryFactor = 10)
{
return createObjectGroups(group, player, constraints, amount, retryFactor, true);
}
function createObjectGroupsByAreasDeprecated(group, player, constraints, amount, retryFactor, areas)
{
return createObjectGroupsByAreas(group, player, constraints, amount, retryFactor, areas, true);
}
/**
* Attempts to place the given number of areas in random places of the map.
* Returns actually placed areas.
*/
function createAreas(centeredPlacer, painter, constraints, amount, retryFactor = 10)
{
let placeFunc = function() {
centeredPlacer.setCenterPosition(g_Map.randomCoordinate(false));
return createArea(centeredPlacer, painter, constraints);
};
return retryPlacing(placeFunc, retryFactor, amount, false);
}
/**
* Attempts to place the given number of areas in random places of the given areas.
* Returns actually placed areas.
*/
function createAreasInAreas(centeredPlacer, painter, constraints, amount, retryFactor, areas)
{
areas = areas.filter(area => area.getPoints().length);
if (!areas.length) {
log("createAreasInAreas: 'areas' was either empty or only contained empty areas thus returning an empty array.\n" + new Error().stack)
return [];
}
let placeFunc = function() {
centeredPlacer.setCenterPosition(pickRandom(pickRandom(areas).getPoints()));
return createArea(centeredPlacer, painter, constraints);
};
return retryPlacing(placeFunc, retryFactor, amount, false);
}
/**
* Attempts to place the given number of groups in random places of the map.
* Returns the number of actually placed groups.
*/
function createObjectGroups(group, player, constraints, amount, retryFactor = 10, behaveDeprecated = false)
{
let placeFunc = function() {
group.setCenterPosition(g_Map.randomCoordinate(true));
return createObjectGroup(group, player, constraints);
};
return retryPlacing(placeFunc, retryFactor, amount, behaveDeprecated);
}
/**
* Attempts to place the given number of groups in random places of the given areas.
* Returns the number of actually placed groups.
*/
function createObjectGroupsByAreas(group, player, constraints, amount, retryFactor, areas, behaveDeprecated = false)
{
areas = areas.filter(area => area.getPoints().length);
if (!areas.length) {
log("createObjectGroupsByAreas: 'areas' was either empty or only contained empty areas.\n" + new Error().stack)
return [];
}
let placeFunc = function() {
group.setCenterPosition(pickRandom(pickRandom(areas).getPoints()));
return createObjectGroup(group, player, constraints);
};
return retryPlacing(placeFunc, retryFactor, amount, behaveDeprecated);
}
function createTerrain(terrain)
{
return typeof terrain == "string" ?
new SimpleTerrain(...terrain.split(TERRAIN_SEPARATOR)) :
new RandomTerrain(terrain.map(t => createTerrain(t)));
}
/**
* Constructs a new Area shaped by the Placer meeting the Constraints and calls the Painters there.
* Supports both Centered and Non-Centered Placers.
*/
function createArea(placer, painters, constraints)
{
let points = placer.place(new AndConstraint(constraints));
if (!points)
return undefined;
let area = new Area(points);
new MultiPainter(painters).paint(area);
return area;
}
/**
* @param mode is one of the HeightPlacer constants determining whether to exclude the min/max elevation.
*/
function paintTerrainBasedOnHeight(minHeight, maxHeight, mode, terrain)
{
return createArea(
new HeightPlacer(mode, minHeight, maxHeight),
new TerrainPainter(terrain));
}
function paintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileClass)
{
return createArea(
new HeightPlacer(mode, minHeight, maxHeight),
new TileClassPainter(tileClass));
}
function unPaintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileClass)
{
return createArea(
new HeightPlacer(mode, minHeight, maxHeight),
new TileClassUnPainter(tileClass));
}
/**
* Places the Entities of the given Group if they meet the Constraints
* and sets the given player as the owner.
*/
function createObjectGroup(group, player, constraints)
{
return group.place(player, new AndConstraint(constraints));
}
/**
* Create an avoid constraint for the given classes by the given distances
*/
function avoidClasses(/*class1, dist1, class2, dist2, etc*/)
{
let ar = [];
for (let i = 0; i < arguments.length/2; ++i)
ar.push(new AvoidTileClassConstraint(arguments[2*i], arguments[2*i+1]));
// Return single constraint
if (ar.length == 1)
return ar[0];
return new AndConstraint(ar);
}
/**
* Create a stay constraint for the given classes by the given distances
*/
function stayClasses(/*class1, dist1, class2, dist2, etc*/)
{
let ar = [];
for (let i = 0; i < arguments.length/2; ++i)
ar.push(new StayInTileClassConstraint(arguments[2*i], arguments[2*i+1]));
// Return single constraint
if (ar.length == 1)
return ar[0];
return new AndConstraint(ar);
}
/**
* Create a border constraint for the given classes by the given distances
*/
function borderClasses(/*class1, idist1, odist1, class2, idist2, odist2, etc*/)
{
let ar = [];
for (let i = 0; i < arguments.length/3; ++i)
ar.push(new BorderTileClassConstraint(arguments[3*i], arguments[3*i+1], arguments[3*i+2]));
// Return single constraint
if (ar.length == 1)
return ar[0];
return new AndConstraint(ar);
}
/**
* Returns a subset of the given heightmap.
*/
function extractHeightmap(heightmap, topLeft, size)
{
let result = [];
for (let x = 0; x < size; ++x)
{
result[x] = new Float32Array(size);
for (let y = 0; y < size; ++y)
result[x][y] = heightmap[x + topLeft.x][y + topLeft.y];
}
return result;
}
function convertHeightmap1Dto2D(heightmap)
{
let result = [];
let hmSize = Math.sqrt(heightmap.length);
for (let x = 0; x < hmSize; ++x)
{
result[x] = new Float32Array(hmSize);
for (let y = 0; y < hmSize; ++y)
result[x][y] = heightmap[y * hmSize + x];
}
return result;
}
function getDifficulties()
{
return Engine.ReadJSONFile("simulation/data/settings/trigger_difficulties.json").Data;
}
/**
* Returns the numeric difficulty level the player chose.
*/
function getDifficulty()
{
let level = g_MapSettings.TriggerDifficulty || 3;
return getDifficulties().find(difficulty => difficulty.Difficulty == level).Difficulty;
}