Fix wrong default in "scaleByMapArea"
[0ad.git] / binaries / data / mods / public / maps / random / rmgen / library.js
blob20c8c6e7b90fe400f34bb9bc07fda1197d701e25
1 /**
2  * A Centered Placer generates a shape (array of Vector2D points) around a variable center location satisfying a Constraint.
3  * The center can be modified externally using setCenterPosition, typically called by createAreas.
4  */
5 Engine.LoadLibrary("rmgen/placer/centered");
7 /**
8  * A Non-Centered Placer generates a shape (array of Vector2D points) at a fixed location meeting a Constraint and
9  * is typically called by createArea.
10  * Since this type of Placer has no x and z property, its location cannot be randomized using createAreas.
11  */
12 Engine.LoadLibrary("rmgen/placer/noncentered");
14 /**
15  * A Painter modifies an arbitrary feature in a given Area, for instance terrain textures, elevation or calling other painters on that Area.
16  * Typically the area is determined by a Placer called from createArea or createAreas.
17  */
18 Engine.LoadLibrary("rmgen/painter");
20 const TERRAIN_SEPARATOR = "|";
21 const SEA_LEVEL = 20.0;
22 const HEIGHT_UNITS_PER_METRE = 92;
24 /**
25  * Constants needed for heightmap_manipulation.js
26  */
27 const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE; // Engine limit, Roughly 700 meters
28 const MIN_HEIGHT = - SEA_LEVEL;
30 const MAX_HEIGHT = MAX_HEIGHT_RANGE - SEA_LEVEL;
32 /**
33  * Default angle for buildings.
34  */
35 const BUILDING_ORIENTATION = -1/4 * Math.PI;
37 const g_CivData = deepfreeze(loadCivFiles(false));
39 const g_ActorPrefix = "actor|";
41 /**
42  * Sets whether setHeight operates on the center of a tile or on the vertices.
43  */
44 var TILE_CENTERED_HEIGHT_MAP = false;
46 function actorTemplate(templateName)
48         return g_ActorPrefix + templateName + ".xml";
51 function getObstructionSize(templateName, margin = 0)
53         const obstruction = Engine.GetTemplate(templateName).Obstruction;
55         const obstructionSize =
56                 obstruction.Static ?
57                         new Vector2D(obstruction.Static["@depth"], obstruction.Static["@width"]) :
58                 // Used for gates, should consider the position too
59                 obstruction.Obstructions ?
60                         new Vector2D(
61                                 Object.keys(obstruction.Obstructions).reduce((depth, key) => Math.max(depth, +obstruction.Obstructions[key]["@depth"]), 0),
62                                 Object.keys(obstruction.Obstructions).reduce((width, key) => width + +obstruction.Obstructions[key]["@width"], 0)) :
63                         new Vector2D(0, 0);
65         return obstructionSize.div(TERRAIN_TILE_SIZE).add(new Vector2D(2, 2).mult(margin));
68 function fractionToTiles(f)
70         return g_MapSettings.Size * f;
73 function tilesToFraction(t)
75         return t / g_MapSettings.Size;
78 function scaleByMapSize(min, max, minMapSize = 128, maxMapSize = 512)
80         return min + (max - min) * (g_MapSettings.Size - minMapSize) / (maxMapSize - minMapSize);
83 /**
84  * Interpolate quadratic between (min, minMapArea) and (max, maxMapArea) with respect to the mapSize.
85  * Default values set on the area of tiny and giant map sizes according to the map shape (square or circular).
86  */
87 function scaleByMapArea(min, max, minMapArea = g_Map.getArea(128), maxMapArea = g_Map.getArea(512))
89         return min + (max - min) * (g_Map.getArea() - minMapArea) / (maxMapArea - minMapArea);
92 /**
93  * Interpolate quadraticly between (0,0) and (base, baseArea) with respect to the map size.
94  * @param base - Value we should attain at baseArea.
95  * @param disallowedArea - Area deducted from the map area.
96  * @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).
97  */
98 function scaleByMapAreaAbsolute(base, disallowedArea = 0, baseArea = g_Map.getArea(128))
100         return scaleByMapArea(0, base, disallowedArea, baseArea + disallowedArea);
103 function randomPositionOnTile(tilePosition)
105         return Vector2D.add(tilePosition, new Vector2D(randFloat(0, 1), randFloat(0, 1)));
109  * Retries the given function with those arguments as often as specified.
110  */
111 function retryPlacing(placeFunc, retryFactor, amount, behaveDeprecated = false)
113         const maxFail = amount * retryFactor;
115         const results = [];
116         let bad = 0;
118         while (results.length < amount && bad <= maxFail)
119         {
120                 const result = placeFunc();
122                 if (result !== undefined || behaveDeprecated)
123                         results.push(result);
124                 else
125                         ++bad;
126         }
128         return results;
131 // TODO this is a hack to simulate the old behaviour of those functions
132 // until all old maps are changed to use the correct version of these functions
133 function createObjectGroupsDeprecated(group, player, constraints, amount, retryFactor = 10)
135         return createObjectGroups(group, player, constraints, amount, retryFactor, true);
138 function createObjectGroupsByAreasDeprecated(group, player, constraints, amount, retryFactor, areas)
140         return createObjectGroupsByAreas(group, player, constraints, amount, retryFactor, areas, true);
144  * Attempts to place the given number of areas in random places of the map.
145  * Returns actually placed areas.
146  */
147 function createAreas(centeredPlacer, painter, constraints, amount, retryFactor = 10)
149         const placeFunc = function() {
150                 centeredPlacer.setCenterPosition(g_Map.randomCoordinate(false));
151                 return createArea(centeredPlacer, painter, constraints);
152         };
154         return retryPlacing(placeFunc, retryFactor, amount, false);
158  * Attempts to place the given number of areas in random places of the given areas.
159  * Returns actually placed areas.
160  */
161 function createAreasInAreas(centeredPlacer, painter, constraints, amount, retryFactor, areas)
163         areas = areas.filter(area => area.getPoints().length);
164         if (!areas.length) {
165                 log("createAreasInAreas: 'areas' was either empty or only contained empty areas thus returning an empty array.\n" + new Error().stack);
166                 return [];
167         }
169         const placeFunc = function() {
170                 centeredPlacer.setCenterPosition(pickRandom(pickRandom(areas).getPoints()));
171                 return createArea(centeredPlacer, painter, constraints);
172         };
174         return retryPlacing(placeFunc, retryFactor, amount, false);
178  * Attempts to place the given number of groups in random places of the map.
179  * Returns the number of actually placed groups.
180  */
181 function createObjectGroups(group, player, constraints, amount, retryFactor = 10, behaveDeprecated = false)
183         const placeFunc = function() {
184                 group.setCenterPosition(g_Map.randomCoordinate(true));
185                 return createObjectGroup(group, player, constraints);
186         };
188         return retryPlacing(placeFunc, retryFactor, amount, behaveDeprecated);
192  * Attempts to place the given number of groups in random places of the given areas.
193  * Returns the number of actually placed groups.
194  */
195 function createObjectGroupsByAreas(group, player, constraints, amount, retryFactor, areas, behaveDeprecated = false)
197         areas = areas.filter(area => area.getPoints().length);
198         if (!areas.length) {
199                 log("createObjectGroupsByAreas: 'areas' was either empty or only contained empty areas.\n" + new Error().stack);
200                 return [];
201         }
203         const placeFunc = function() {
204                 group.setCenterPosition(pickRandom(pickRandom(areas).getPoints()));
205                 return createObjectGroup(group, player, constraints);
206         };
208         return retryPlacing(placeFunc, retryFactor, amount, behaveDeprecated);
211 function createTerrain(terrain)
213         return typeof terrain == "string" ?
214                 new SimpleTerrain(...terrain.split(TERRAIN_SEPARATOR)) :
215                 new RandomTerrain(terrain.map(t => createTerrain(t)));
219  * Constructs a new Area shaped by the Placer meeting the Constraints and calls the Painters there.
220  * Supports both Centered and Non-Centered Placers.
221  */
222 function createArea(placer, painters, constraints)
224         const points = placer.place(new AndConstraint(constraints));
225         if (!points)
226                 return undefined;
228         const area = new Area(points);
230         new MultiPainter(painters).paint(area);
232         return area;
236  * @param mode is one of the HeightPlacer constants determining whether to exclude the min/max elevation.
237  */
238 function paintTerrainBasedOnHeight(minHeight, maxHeight, mode, terrain)
240         return createArea(
241                 new HeightPlacer(mode, minHeight, maxHeight),
242                 new TerrainPainter(terrain));
245 function paintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileClass)
247         return createArea(
248                 new HeightPlacer(mode, minHeight, maxHeight),
249                 new TileClassPainter(tileClass));
252 function unPaintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileClass)
254         return createArea(
255                 new HeightPlacer(mode, minHeight, maxHeight),
256                 new TileClassUnPainter(tileClass));
260  * Places the Entities of the given Group if they meet the Constraints
261  * and sets the given player as the owner.
262  */
263 function createObjectGroup(group, player, constraints)
265         return group.place(player, new AndConstraint(constraints));
269  * Create an avoid constraint for the given classes by the given distances
270  */
271 function avoidClasses(/*class1, dist1, class2, dist2, etc*/)
273         const ar = [];
274         for (let i = 0; i < arguments.length/2; ++i)
275                 ar.push(new AvoidTileClassConstraint(arguments[2*i], arguments[2*i+1]));
277         // Return single constraint
278         if (ar.length == 1)
279                 return ar[0];
281         return new AndConstraint(ar);
285  * Create a stay constraint for the given classes by the given distances
286  */
287 function stayClasses(/*class1, dist1, class2, dist2, etc*/)
289         const ar = [];
290         for (let i = 0; i < arguments.length/2; ++i)
291                 ar.push(new StayInTileClassConstraint(arguments[2*i], arguments[2*i+1]));
293         // Return single constraint
294         if (ar.length == 1)
295                 return ar[0];
297         return new AndConstraint(ar);
301  * Create a border constraint for the given classes by the given distances
302  */
303 function borderClasses(/*class1, idist1, odist1, class2, idist2, odist2, etc*/)
305         const ar = [];
306         for (let i = 0; i < arguments.length/3; ++i)
307                 ar.push(new BorderTileClassConstraint(arguments[3*i], arguments[3*i+1], arguments[3*i+2]));
309         // Return single constraint
310         if (ar.length == 1)
311                 return ar[0];
313         return new AndConstraint(ar);
317  * Returns a subset of the given heightmap.
318  */
319 function extractHeightmap(heightmap, topLeft, size)
321         const result = [];
322         for (let x = 0; x < size; ++x)
323         {
324                 result[x] = new Float32Array(size);
325                 for (let y = 0; y < size; ++y)
326                         result[x][y] = heightmap[x + topLeft.x][y + topLeft.y];
327         }
328         return result;
331 function convertHeightmap1Dto2D(heightmap)
333         const result = [];
334         const hmSize = Math.sqrt(heightmap.length);
335         for (let x = 0; x < hmSize; ++x)
336         {
337                 result[x] = new Float32Array(hmSize);
338                 for (let y = 0; y < hmSize; ++y)
339                         result[x][y] = heightmap[y * hmSize + x];
340         }
341         return result;
344 function getDifficulties()
346         return Engine.ReadJSONFile("simulation/data/settings/trigger_difficulties.json").Data;
350  * Returns the numeric difficulty level the player chose.
351  */
352 function getDifficulty()
354         const level = g_MapSettings.TriggerDifficulty || 3;
355         return getDifficulties().find(difficulty => difficulty.Difficulty == level).Difficulty;