Merge 'remotes/trunk'
[0ad.git] / binaries / data / mods / public / maps / random / jebel_barkal.js
blobdeaee06649043d0d514be57e9c3ccc93ac3e1fc3
1 /**
2  * For historic reference, see
3  * http://www.jebelbarkal.org/images/maps/siteplan.jpg
4  */
6 Engine.LoadLibrary("rmgen");
7 Engine.LoadLibrary("rmgen-common");
8 Engine.LoadLibrary("heightmap");
10 function* GenerateMap(mapSettings)
12         TILE_CENTERED_HEIGHT_MAP = true;
14         const tSand = "desert_sand_dunes_100";
15         const tHilltop = ["new_savanna_dirt_c", "new_savanna_dirt_d"];
16         const tHillGround = ["savanna_dirt_rocks_a", "savanna_dirt_rocks_b", "savanna_dirt_rocks_c"];
17         const tHillCliff = ["savanna_cliff_a_red", "savanna_cliff_b_red"];
18         const tRoadDesert = "savanna_tile_a";
19         const tRoadFertileLand = "savanna_tile_a";
20         const tWater = "desert_sand_wet";
21         const tGrass =
22                 ["savanna_shrubs_a_wetseason", "alpine_grass_b_wild", "medit_shrubs_a", "steppe_grass_green_a"];
23         const tForestFloorFertile = pickRandom(tGrass);
24         const tGrassTransition1 = "desert_grass_a";
25         const tGrassTransition2 = "steppe_grass_dirt_66";
26         const tPath = "road2";
27         const tPathWild = "road_med";
29         const oAcacia = "gaia/tree/acacia";
30         const oPalmPath = "gaia/tree/cretan_date_palm_tall";
31         const oPalms = [
32                 "gaia/tree/cretan_date_palm_tall",
33                 "gaia/tree/cretan_date_palm_short",
34                 "gaia/tree/palm_tropic",
35                 "gaia/tree/date_palm",
36                 "gaia/tree/senegal_date_palm",
37                 "gaia/tree/medit_fan_palm"
38         ];
39         const oBerryBushGrapes = "gaia/fruit/grapes";
40         const oBerryBushDesert = "gaia/fruit/berry_05";
41         const oStoneLargeDesert = "gaia/rock/desert_large";
42         const oStoneSmallDesert = "gaia/rock/desert_small";
43         const oMetalLargeDesert = "gaia/ore/desert_large";
44         const oMetalSmallDesert = "gaia/ore/desert_small";
45         const oStoneLargeFertileLand = "gaia/rock/desert_large";
46         const oStoneSmallFertileLand = "gaia/rock/greece_small";
47         const oMetalLargeFertileLand = "gaia/ore/desert_large";
48         const oMetalSmallFertileLand = "gaia/ore/temperate_small";
49         const oFoodTreasureBin = "gaia/treasure/food_bin";
50         const oFoodTreasureCrate = "gaia/treasure/food_crate";
51         const oFoodTreasureJars = "gaia/treasure/food_jars";
52         const oWoodTreasure = "gaia/treasure/wood";
53         const oStoneTreasure = "gaia/treasure/stone";
54         const oMetalTreasure = "gaia/treasure/metal";
55         const oTreasuresHill = [oWoodTreasure, oStoneTreasure, oMetalTreasure];
56         const oTreasuresCity =
57                 [oFoodTreasureBin, oFoodTreasureCrate, oFoodTreasureJars].concat(oTreasuresHill);
58         const oGiraffe = "gaia/fauna_giraffe";
59         const oGiraffeInfant = "gaia/fauna_giraffe_infant";
60         const oGazelle = "gaia/fauna_gazelle";
61         const oRhino = "gaia/fauna_rhinoceros_white";
62         const oWarthog = "gaia/fauna_boar";
63         const oElephant = "gaia/fauna_elephant_african_bush";
64         const oElephantInfant = "gaia/fauna_elephant_african_infant";
65         const oLion = "gaia/fauna_lion";
66         const oLioness = "gaia/fauna_lioness";
67         const oCrocodile = "gaia/fauna_crocodile_nile";
68         const oFish = "gaia/fish/tilapia";
69         const oHawk = "birds/buzzard";
70         const oTempleApedemak = "structures/kush/temple";
71         const oTempleAmun = "structures/kush/temple_amun";
72         const oPyramidLarge = "structures/kush/pyramid_large";
73         const oPyramidSmall = "structures/kush/pyramid_small";
74         const oWonderPtol = "structures/ptol/wonder";
75         const oFortress = "structures/kush/fortress";
76         const oTower = mapSettings.Size >= 256 && getDifficulty() >= 3 ? "structures/kush/defense_tower" :
77                 "structures/kush/sentry_tower";
78         const oHouse = "structures/kush/house";
79         const oMarket = "structures/kush/market";
80         const oForge = "structures/kush/forge";
81         const oBlemmyeCamp = "structures/kush/camp_blemmye";
82         const oNobaCamp = "structures/kush/camp_noba";
83         const oCivicCenter = "structures/kush/civil_centre";
84         const oBarracks = "structures/kush/barracks";
85         const oStable = "structures/kush/stable";
86         const oElephantStable = "structures/kush/elephant_stable";
87         const oWallMedium = "structures/kush/wall_medium";
88         const oWallGate = "structures/kush/wall_gate";
89         const oWallTower = "structures/kush/wall_tower";
90         const oPalisadeMedium = "structures/palisades_medium";
91         const oPalisadeGate = "structures/palisades_gate";
92         const oPalisadeTower = "structures/palisades_tower";
93         const oKushCitizenArcher = "units/kush/infantry_archer_b";
94         const oKushHealer = "units/kush/support_healer_b";
95         const oKushChampionArcher = "units/kush/champion_infantry_archer";
96         const oKushChampions = [
97                 oKushChampionArcher,
98                 "units/kush/champion_infantry_amun",
99                 "units/kush/champion_infantry_apedemak"
100         ];
101         const oPtolSiege = ["units/ptol/siege_lithobolos_unpacked", "units/ptol/siege_polybolos_unpacked"];
102         const oTriggerPointCityPath = "trigger/trigger_point_A";
103         const oTriggerPointAttackerPatrol = "trigger/trigger_point_B";
105         const aPalmPath = actorTemplate("flora/trees/palm_cretan_date_tall");
106         const aRock = actorTemplate("geology/stone_savanna_med");
107         const aHandcart = actorTemplate("props/special/eyecandy/handcart_1");
108         const aPlotFence = actorTemplate("props/special/common/plot_fence");
109         const aStatueKush = actorTemplate("props/special/eyecandy/statues_kush");
110         const aStatues = [
111                 "props/structures/kushites/statue_pedestal_rectangular",
112                 "props/structures/kushites/statue_pedestal_rectangular_lion"
113         ].map(actorTemplate);
114         const aBushesFertileLand = [
115                 ...new Array(3).fill("props/flora/shrub_spikes"),
116                 ...new Array(3).fill("props/flora/ferns"),
117                 "props/flora/shrub_tropic_plant_a",
118                 "props/flora/shrub_tropic_plant_b",
119                 "props/flora/shrub_tropic_plant_flower",
120                 "props/flora/foliagebush",
121                 "props/flora/bush",
122                 "props/flora/bush_medit_la",
123                 "props/flora/bush_medit_la_lush",
124                 "props/flora/bush_medit_me_lush",
125                 "props/flora/bush_medit_sm",
126                 "props/flora/bush_medit_sm_lush",
127                 "props/flora/bush_tempe_la_lush"
128         ].map(actorTemplate);
129         const aBushesCity = [
130                 "props/flora/bush_dry_a",
131                 "props/flora/bush_medit_la_dry",
132                 "props/flora/bush_medit_me_dry",
133                 "props/flora/bush_medit_sm",
134                 "props/flora/bush_medit_sm_dry",
135         ].map(actorTemplate);
136         const aBushesDesert = [
137                 "props/flora/bush_tempe_me_dry",
138                 "props/flora/grass_soft_dry_large_tall",
139                 "props/flora/grass_soft_dry_small_tall"
140         ].map(actorTemplate).concat(aBushesCity);
141         const aWaterDecoratives = ["props/flora/reeds_pond_lush_a"].map(actorTemplate);
143         const pForestPalms = [
144                 tForestFloorFertile,
145                 ...oPalms.map(tree => tForestFloorFertile + TERRAIN_SEPARATOR + tree),
146                 tForestFloorFertile];
148         const heightScale = num => num * mapSettings.Size / 320;
150         const minHeightSource = 3;
151         const maxHeightSource = 800;
153         globalThis.g_Map = new RandomMap(0, tSand);
154         const mapSize = g_Map.getSize();
155         const mapCenter = g_Map.getCenter();
156         const mapBounds = g_Map.getBounds();
157         const numPlayers = getNumPlayers();
159         const clHill = g_Map.createTileClass();
160         const clCliff = g_Map.createTileClass();
161         const clDesert = g_Map.createTileClass();
162         const clFertileLand = g_Map.createTileClass();
163         const clWater = g_Map.createTileClass();
164         const clIrrigationCanal = g_Map.createTileClass();
165         const clPassage = g_Map.createTileClass();
166         const clPlayer = g_Map.createTileClass();
167         const clBaseResource = g_Map.createTileClass();
168         const clFood = g_Map.createTileClass();
169         const clForest = g_Map.createTileClass();
170         const clRock = g_Map.createTileClass();
171         const clMetal = g_Map.createTileClass();
172         const clTreasure = g_Map.createTileClass();
173         const clCity = g_Map.createTileClass();
174         const clPath = g_Map.createTileClass();
175         const clPathStatues = g_Map.createTileClass();
176         const clPathCrossing = g_Map.createTileClass();
177         const clStatue = g_Map.createTileClass();
178         const clWall = g_Map.createTileClass();
179         const clGate = g_Map.createTileClass();
180         const clRoad = g_Map.createTileClass();
181         const clTriggerPointCityPath = g_Map.createTileClass();
182         const clTriggerPointMap = g_Map.createTileClass();
183         const clSoldier = g_Map.createTileClass();
184         const clTower = g_Map.createTileClass();
185         const clFortress = g_Map.createTileClass();
186         const clTemple = g_Map.createTileClass();
187         const clRitualPlace = g_Map.createTileClass();
188         const clPyramid = g_Map.createTileClass();
189         const clHouse = g_Map.createTileClass();
190         const clForge = g_Map.createTileClass();
191         const clStable = g_Map.createTileClass();
192         const clElephantStable = g_Map.createTileClass();
193         const clCivicCenter = g_Map.createTileClass();
194         const clBarracks = g_Map.createTileClass();
195         const clBlemmyeCamp = g_Map.createTileClass();
196         const clNobaCamp = g_Map.createTileClass();
197         const clMarket = g_Map.createTileClass();
198         const clDecorative = g_Map.createTileClass();
200         const riverAngle = 0.05 * Math.PI;
202         const hillRadius = scaleByMapSize(40, 120);
203         const positionPyramids = new Vector2D(fractionToTiles(0.15), fractionToTiles(0.75));
205         const pathWidth = 4;
206         const pathWidthCenter = 10;
207         const pathWidthSecondary = 6;
209         const placeNapataWall = mapSize < 192 || getDifficulty() < 2 ? false : getDifficulty() < 3 ?
210                 "napata_palisade" : "napata_wall";
212         const layoutFertileLandTextures = [
213                 {
214                         "left": fractionToTiles(0),
215                         "right": fractionToTiles(0.04),
216                         "terrain": createTerrain(tGrassTransition1),
217                         "tileClass": clFertileLand
218                 },
219                 {
220                         "left": fractionToTiles(0.04),
221                         "right": fractionToTiles(0.08),
222                         "terrain": createTerrain(tGrassTransition2),
223                         "tileClass": clDesert
224                 }
225         ];
227         const layoutKushTemples = [
228                 ...new Array(2).fill(0).map((v, i) =>
229                         ({
230                                 "template": oTempleApedemak,
231                                 "pathOffset": new Vector2D(0, 9),
232                                 "minMapSize": i == 0 ? 320 : 0
233                         })),
234                 {
235                         "template": oTempleAmun,
236                         "pathOffset": new Vector2D(0, 12),
237                         "minMapSize": 256
238                 },
239                 {
240                         "template": oWonderPtol,
241                         "pathOffset": new Vector2D(0, scaleByMapSize(9, 14)),
242                         "minMapSize": 0
243                 },
244                 {
245                         "template": oTempleAmun,
246                         "pathOffset": new Vector2D(0, 12),
247                         "minMapSize": 256
248                 },
249                 ...new Array(2).fill(0).map((v, i) =>
250                         ({
251                                 "template": oTempleApedemak,
252                                 "pathOffset": new Vector2D(0, 9),
253                                 "minMapSize": i == 0 ? 320 : 0
254                         }))
255         ].filter(temple => mapSize >= temple.minMapSize);
257         /**
258          * The buildings are set as uncapturable, otherwise the player would gain
259          * the buildings via root territory and can delete them without effort.
260          * Keep the entire city uncapturable as a consistent property of the city.
261          */
262         const layoutKushCity = [
263                 {
264                         "templateName": "uncapturable|" + oHouse,
265                         "difficulty": "Very Easy",
266                         "painters": new TileClassPainter(clHouse)
267                 },
268                 {
269                         "templateName": oFortress,
270                         "difficulty": "Medium",
271                         "constraints": [avoidClasses(clFortress, 25), new NearTileClassConstraint(clPath, 8)],
272                         "painters": new TileClassPainter(clFortress)
273                 },
274                 {
275                         "templateName": oCivicCenter,
276                         "difficulty": "Easy",
277                         "constraints": [avoidClasses(clCivicCenter, 60), new NearTileClassConstraint(clPath, 8)],
278                         "painters": new TileClassPainter(clCivicCenter)
279                 },
280                 {
281                         "templateName": oElephantStable,
282                         "difficulty": "Easy",
283                         "constraints": avoidClasses(clElephantStable, 10),
284                         "painters": new TileClassPainter(clElephantStable)
285                 },
286                 {
287                         "templateName": oStable,
288                         "difficulty": "Easy",
289                         "constraints": avoidClasses(clStable, 20),
290                         "painters": new TileClassPainter(clStable)
291                 },
292                 {
293                         "templateName": oBarracks,
294                         "difficulty": "Easy",
295                         "constraints": avoidClasses(clBarracks, 12),
296                         "painters": new TileClassPainter(clBarracks)
297                 },
298                 {
299                         "templateName": oTower,
300                         "difficulty": "Easy",
301                         "constraints": avoidClasses(clTower, 17),
302                         "painters": new TileClassPainter(clTower)
303                 },
304                 {
305                         "templateName": "uncapturable|" + oMarket,
306                         "difficulty": "Very Easy",
307                         "constraints": avoidClasses(clMarket, 15),
308                         "painters": new TileClassPainter(clMarket)
309                 },
310                 {
311                         "templateName": "uncapturable|" + oForge,
312                         "difficulty": "Very Easy",
313                         "constraints": avoidClasses(clForge, 30),
314                         "painters": new TileClassPainter(clForge)
315                 },
316                 {
317                         "templateName": oNobaCamp,
318                         "difficulty": "Easy",
319                         "constraints": avoidClasses(clNobaCamp, 30),
320                         "painters": new TileClassPainter(clNobaCamp)
321                 },
322                 {
323                         "templateName": oBlemmyeCamp,
324                         "difficulty": "Easy",
325                         "constraints": avoidClasses(clBlemmyeCamp, 30),
326                         "painters": new TileClassPainter(clBlemmyeCamp)
327                 }
328         ].filter(building => getDifficulty() >=
329                 getDifficulties().find(difficulty => difficulty.Name == building.difficulty).Difficulty);
331         g_WallStyles.napata_wall = {
332                 "short": readyWallElement("uncapturable|" + oWallMedium),
333                 "medium": readyWallElement("uncapturable|" + oWallMedium),
334                 "tower": readyWallElement("uncapturable|" + oWallTower),
335                 "gate": readyWallElement("uncapturable|" + oWallGate),
336                 "overlap": 0.05
337         };
339         g_WallStyles.napata_palisade = {
340                 "short": readyWallElement("uncapturable|" + oPalisadeMedium),
341                 "medium": readyWallElement("uncapturable|" + oPalisadeMedium),
342                 "tower": readyWallElement("uncapturable|" + oPalisadeTower),
343                 "gate": readyWallElement("uncapturable|" + oPalisadeGate),
344                 "overlap": 0.05
345         };
347         yield 10;
349         g_Map.log("Loading hill heightmap");
350         createArea(
351                 new MapBoundsPlacer(),
352                 new HeightmapPainter(
353                         translateHeightmap(
354                                 new Vector2D(-12, scaleByMapSize(-12, -25)),
355                                 undefined,
356                                 convertHeightmap1Dto2D(
357                                         Engine.LoadMapTerrain("maps/random/jebel_barkal.pmp").height)),
358                         minHeightSource,
359                         maxHeightSource));
361         const heightDesert = g_Map.getHeight(mapCenter);
362         const heightFertileLand = heightDesert - heightScale(2);
363         const heightShoreline = heightFertileLand - heightScale(0.5);
364         const heightWaterLevel = heightFertileLand - heightScale(3);
365         const heightPassage = heightWaterLevel - heightScale(1.5);
366         const heightIrrigationCanal = heightWaterLevel - heightScale(4);
367         const heightSeaGround = heightWaterLevel - heightScale(8);
368         const heightHill = heightDesert + heightScale(4);
369         const heightHilltop = heightHill + heightScale(90);
370         const heightHillArchers = (heightHilltop + heightHill) / 2;
371         const heightOffsetPath = heightScale(-2.5);
372         const heightOffsetRoad = heightScale(-1.5);
373         const heightOffsetWalls = heightScale(2.5);
374         const heightOffsetStatue = heightScale(2.5);
376         g_Map.log("Flattening land");
377         createArea(
378                 new MapBoundsPlacer(),
379                 new ElevationPainter(heightDesert),
380                 new HeightConstraint(-Infinity, heightDesert));
382         // Fertile land
383         const widthFertileLand = fractionToTiles(0.33);
384         paintRiver({
385                 "parallel": true,
386                 "start": new Vector2D(mapBounds.left, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
387                 "end": new Vector2D(mapBounds.right, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
388                 "width": 2 * widthFertileLand,
389                 "fadeDist": 8,
390                 "deviation": 0,
391                 "heightLand": heightDesert,
392                 "heightRiverbed": heightFertileLand,
393                 "meanderShort": 40,
394                 "meanderLong": 0,
395                 "waterFunc": (position, height, riverFraction) => {
396                         createTerrain(tGrass).place(position);
397                         clFertileLand.add(position);
398                 },
399                 "landFunc": (position, shoreDist1, shoreDist2) => {
401                         for (const riv of layoutFertileLandTextures)
402                                 if (riv.left < +shoreDist1 && +shoreDist1 < riv.right ||
403                                     riv.left < -shoreDist2 && -shoreDist2 < riv.right)
404                                 {
405                                         riv.tileClass.add(position);
406                                         riv.terrain.place(position);
407                                 }
408                 }
409         });
411         // Nile
412         paintRiver({
413                 "parallel": true,
414                 "start": new Vector2D(mapBounds.left, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
415                 "end": new Vector2D(mapBounds.right, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
416                 "width": fractionToTiles(0.2),
417                 "fadeDist": 4,
418                 "deviation": 0,
419                 "heightLand": heightFertileLand,
420                 "heightRiverbed": heightSeaGround,
421                 "meanderShort": 40,
422                 "meanderLong": 0
423         });
424         yield 30;
426         g_Map.log("Computing player locations");
427         const playerIDs = sortAllPlayers();
428         const playerPosition = playerPlacementArcs(
429                 playerIDs,
430                 mapCenter,
431                 fractionToTiles(0.38),
432                 riverAngle - 0.5 * Math.PI,
433                 0.05 * Math.PI,
434                 0.55 * Math.PI);
436         if (!isNomad())
437         {
438                 g_Map.log("Marking player positions");
439                 for (const position of playerPosition)
440                         addCivicCenterAreaToClass(position, clPlayer);
441         }
443         g_Map.log("Marking water");
444         createArea(
445                 new MapBoundsPlacer(),
446                 [
447                         new TileClassPainter(clWater),
448                         new TileClassUnPainter(clFertileLand)
449                 ],
450                 new HeightConstraint(-Infinity, heightWaterLevel));
452         g_Map.log("Marking desert");
453         const avoidWater = new StaticConstraint(avoidClasses(clWater, 0));
454         createArea(
455                 new MapBoundsPlacer(),
456                 new TileClassPainter(clDesert),
457                 [
458                         new HeightConstraint(-Infinity, heightHill),
459                         avoidWater,
460                         avoidClasses(clFertileLand, 0)
461                 ]);
463         const stayDesert = new StaticConstraint(stayClasses(clDesert, 0));
464         const stayFertileLand = new StaticConstraint(stayClasses(clFertileLand, 0));
466         g_Map.log("Finding possible irrigation canal locations");
467         const irrigationCanalAreas = [];
468         for (let i = 0; i < 30; ++i)
469         {
470                 const x = fractionToTiles(randFloat(0, 1));
471                 irrigationCanalAreas.push(
472                         createArea(
473                                 new PathPlacer(
474                                         new Vector2D(x, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
475                                         new Vector2D(x, mapBounds.top).rotateAround(-riverAngle, mapCenter),
476                                         3,
477                                         0,
478                                         10,
479                                         0.1,
480                                         0.01,
481                                         Infinity),
482                                 undefined,
483                                 avoidClasses(clDesert, 2)));
484         }
486         g_Map.log("Creating irrigation canals");
487         const irrigationCanalLocations = [];
488         for (const area of irrigationCanalAreas)
489         {
490                 if (!area.getPoints().length ||
491                         area.getPoints().some(point => !avoidClasses(
492                                 clPlayer, scaleByMapSize(8, 13),
493                                 clIrrigationCanal, scaleByMapSize(15, 25)).allows(point)))
494                         continue;
496                 irrigationCanalLocations.push(
497                         pickRandom(area.getPoints()).clone().rotateAround(riverAngle, mapCenter).x);
498                 createArea(
499                         new MapBoundsPlacer(),
500                         [
501                                 new SmoothElevationPainter(ELEVATION_SET, heightIrrigationCanal, 1),
502                                 new TileClassPainter(clIrrigationCanal)
503                         ],
504                         [
505                                 new StayAreasConstraint([area]),
506                                 new HeightConstraint(heightIrrigationCanal, heightDesert)
507                         ]);
508         }
510         g_Map.log("Creating passages");
511         let previousPassageY = randIntInclusive(0, widthFertileLand);
512         const areasPassages = [];
513         irrigationCanalLocations.sort((a, b) => a - b);
514         for (let i = 0; i < irrigationCanalLocations.length; ++i)
515         {
516                 const previous = i == 0 ? mapBounds.left : irrigationCanalLocations[i - 1];
517                 const next = i == irrigationCanalLocations.length - 1 ? mapBounds.right :
518                         irrigationCanalLocations[i + 1];
520                 const x1 = (irrigationCanalLocations[i] + previous) / 2;
521                 const x2 = (irrigationCanalLocations[i] + next) / 2;
522                 let y;
524                 // The passages should be at different locations, so that enemies
525                 // can't attack each other easily
526                 for (let tries = 0; tries < 100; ++tries)
527                 {
528                         y = (previousPassageY +
529                                 randIntInclusive(0.2 * widthFertileLand, 0.8 * widthFertileLand)) %
530                                 widthFertileLand;
532                         const pos = new Vector2D((x1 + x2) / 2, y).rotateAround(-riverAngle, mapCenter).round();
534                         if (g_Map.validTilePassable(new Vector2D(pos.x, pos.y)) &&
535                                 avoidClasses(clDesert, 12).allows(pos) &&
536                                 new HeightConstraint(heightIrrigationCanal, heightFertileLand).allows(pos))
537                                 break;
538                 }
540                 const area =
541                         createArea(
542                                 new PathPlacer(
543                                         new Vector2D(x1, y).rotateAround(-riverAngle, mapCenter),
544                                         new Vector2D(x2, y).rotateAround(-riverAngle, mapCenter),
545                                         10,
546                                         0,
547                                         1,
548                                         0,
549                                         0,
550                                         Infinity),
551                                 [
552                                         new ElevationPainter(heightPassage),
553                                         new TileClassPainter(clPassage)
554                                 ],
555                                 [
556                                         new HeightConstraint(-Infinity, heightPassage),
557                                         stayClasses(clFertileLand, 2)
558                                 ]);
560                 if (!area || !area.getPoints().length)
561                         continue;
563                 previousPassageY = y;
564                 areasPassages.push(area);
565         }
566         yield 40;
568         g_Map.log("Marking hill");
569         createArea(
570                 new MapBoundsPlacer(),
571                 new TileClassPainter(clHill),
572                 new HeightConstraint(heightHill, Infinity));
574         g_Map.log("Marking water");
575         const areaWater = createArea(
576                 new MapBoundsPlacer(),
577                 new TileClassPainter(clWater),
578                 new HeightConstraint(-Infinity, heightWaterLevel));
580         g_Map.log("Painting water and shoreline");
581         createArea(
582                 new MapBoundsPlacer(),
583                 new TerrainPainter(tWater),
584                 new HeightConstraint(-Infinity, heightShoreline));
586         g_Map.log("Painting hill");
587         const areaHill = createArea(
588                 new MapBoundsPlacer(),
589                 new TerrainPainter(tHillGround),
590                 new HeightConstraint(heightHill, Infinity));
592         g_Map.log("Painting hilltop");
593         const areaHilltop = createArea(
594                 new MapBoundsPlacer(),
595                 new TerrainPainter(tHilltop),
596                 [
597                         new HeightConstraint(heightHilltop, Infinity),
598                         new SlopeConstraint(-Infinity, 2)
599                 ]);
601         yield 50;
603         for (let i = 0; i < numPlayers; ++i)
604         {
605                 const isDesert = clDesert.has(playerPosition[i]);
606                 placePlayerBase({
607                         "playerID": playerIDs[i],
608                         "playerPosition": playerPosition[i],
609                         "PlayerTileClass": clPlayer,
610                         "BaseResourceClass": clBaseResource,
611                         "baseResourceConstraint": avoidClasses(clPlayer, 4, clWater, 4),
612                         "Walls": mapSize <= 256 || getDifficulty() >= 3 ? "towers" : "walls",
613                         "CityPatch": {
614                                 "outerTerrain": isDesert ? tRoadDesert : tRoadFertileLand,
615                                 "innerTerrain": isDesert ? tRoadDesert : tRoadFertileLand
616                         },
617                         "StartingAnimal": {
618                                 "template": oGazelle,
619                                 "distance": 15,
620                                 "minGroupDistance": 2,
621                                 "maxGroupDistance": 4,
622                                 "minGroupCount": 2,
623                                 "maxGroupCount": 3
624                         },
625                         "Berries": {
626                                 "template": isDesert ? oBerryBushDesert : oBerryBushGrapes
627                         },
628                         "Mines": {
629                                 "types": [
630                                         { "template": isDesert ? oMetalLargeDesert : oMetalLargeFertileLand },
631                                         { "template": isDesert ? oStoneLargeDesert : oStoneLargeFertileLand }
632                                 ]
633                         },
634                         "Trees": {
635                                 "template": isDesert ? oAcacia : pickRandom(oPalms),
636                                 "count": isDesert ? scaleByMapSize(5, 10) : scaleByMapSize(15, 30)
637                         },
638                         "Treasures": {
639                                 "types":
640                                 [
641                                         {
642                                                 "template": oWoodTreasure,
643                                                 "count": isDesert ? 4 : 0
644                                         },
645                                         {
646                                                 "template": oStoneTreasure,
647                                                 "count": isDesert ? 1 : 0
648                                         },
649                                         {
650                                                 "template": oMetalTreasure,
651                                                 "count": isDesert ? 1 : 0
652                                         }
653                                 ]
654                         },
655                         "Decoratives": {
656                                 "template": isDesert ? aRock : pickRandom(aBushesFertileLand)
657                         }
658                 });
659         }
661         g_Map.log("Placing pyramids");
662         const areaPyramids = createArea(new DiskPlacer(scaleByMapSize(5, 14), positionPyramids));
663         // Retry loops are needed due to the self-avoidance
664         createObjectGroupsByAreas(
665                 new SimpleGroup(
666                         [new RandomObject(
667                                 [oPyramidLarge, oPyramidSmall],
668                                 scaleByMapSize(1, 6),
669                                 scaleByMapSize(2, 8),
670                                 scaleByMapSize(6, 8),
671                                 scaleByMapSize(6, 14),
672                                 Math.PI * 1.35,
673                                 Math.PI * 1.5,
674                                 scaleByMapSize(6, 8))],
675                         true,
676                         clPyramid),
677                 0,
678                 undefined,
679                 1,
680                 50,
681                 [areaPyramids]);
683         yield 60;
685         // The city is a circle segment of this maximum size
686         g_Map.log("Computing city grid");
687         const gridCenter = new Vector2D(0, fractionToTiles(0.3)).rotate(-riverAngle).add(mapCenter).round();
688         const gridMaxAngle = Math.min(scaleByMapSize(1/3, 1), 2/3) * Math.PI;
689         const gridStartAngle = -Math.PI / 2 -gridMaxAngle / 2 + riverAngle;
690         const gridRadius = y => hillRadius + 18 * y;
692         const gridPointsX = layoutKushTemples.length;
693         const gridPointsY = Math.floor(scaleByMapSize(2, 5));
694         const gridPointXCenter = Math.floor(gridPointsX / 2);
695         const gridPointYCenter = Math.floor(gridPointsY / 2);
697         // Maps from grid position to map position
698         const cityGridPosition = [];
699         const cityGridAngle = [];
700         for (let y = 0; y < gridPointsY; ++y)
701                 [cityGridPosition[y], cityGridAngle[y]] = distributePointsOnCircularSegment(
702                         gridPointsX, gridMaxAngle, gridStartAngle, gridRadius(y), gridCenter);
704         g_Map.log("Marking city path crossings");
705         for (const y in cityGridPosition)
706                 for (const x in cityGridPosition[y])
707                 {
708                         cityGridPosition[y][x].round();
709                         createArea(
710                                 new DiskPlacer(pathWidth, cityGridPosition[y][x]),
711                                 [
712                                         new TileClassPainter(clPath),
713                                         new TileClassPainter(clPathCrossing)
714                                 ]);
715                 }
717         g_Map.log("Marking horizontal city paths");
718         const areasCityPaths = [];
719         for (let y = 0; y < gridPointsY; ++y)
720                 for (let x = 1; x < gridPointsX; ++x)
721                 {
722                         const width = y == gridPointYCenter ? pathWidthSecondary : pathWidth;
723                         areasCityPaths.push(
724                                 createArea(
725                                         new PathPlacer(cityGridPosition[y][x - 1], cityGridPosition[y][x], width, 0,
726                                                 8, 0, 0, Infinity),
727                                         new TileClassPainter(clPath)));
728                 }
730         g_Map.log("Marking vertical city paths");
731         for (let y = 1; y < gridPointsY; ++y)
732                 for (let x = 0; x < gridPointsX; ++x)
733                 {
734                         const width =
735                                 Math.abs(x - gridPointXCenter) == 0 ? pathWidthCenter :
736                                         Math.abs(x - gridPointXCenter) == 1 ? pathWidthSecondary : pathWidth;
738                         areasCityPaths.push(
739                                 createArea(
740                                         new PathPlacer(cityGridPosition[y - 1][x], cityGridPosition[y][x], width, 0,
741                                                 8, 0, 0, Infinity),
742                                         new TileClassPainter(clPath)));
743                 }
744         yield 70;
746         g_Map.log("Placing kushite temples");
747         const entitiesTemples = [];
748         const templePosition = [];
749         for (let i = 0; i < layoutKushTemples.length; ++i)
750         {
751                 const x = i + (gridPointsX - layoutKushTemples.length) / 2;
752                 templePosition[i] = Vector2D.add(cityGridPosition[0][x],
753                         layoutKushTemples[i].pathOffset.rotate(-Math.PI / 2 - cityGridAngle[0][x]));
754                 entitiesTemples[i] = g_Map.placeEntityPassable(layoutKushTemples[i].template, 0,
755                         templePosition[i], cityGridAngle[0][x]);
756         }
758         g_Map.log("Marking temple area");
759         createArea(
760                 new EntitiesObstructionPlacer(entitiesTemples, 0, Infinity),
761                 new TileClassPainter(clTemple));
763         g_Map.log("Smoothing temple ground");
764         createArea(
765                 new MapBoundsPlacer(),
766                 new ElevationBlendingPainter(heightDesert, 0.8),
767                 new NearTileClassConstraint(clTemple, 0));
769         g_Map.log("Painting cliffs");
770         createArea(
771                 new MapBoundsPlacer(),
772                 [
773                         new TerrainPainter(tHillCliff),
774                         new TileClassPainter(clCliff)
775                 ],
776                 [
777                         stayClasses(clHill, 0),
778                         new SlopeConstraint(2, Infinity)
779                 ]);
781         g_Map.log("Painting temple ground");
782         createArea(
783                 new MapBoundsPlacer(),
784                 new TerrainPainter(tPathWild),
785                 [
786                         new NearTileClassConstraint(clTemple, 1),
787                         avoidClasses(clPath, 0, clCliff, 1)
788                 ]);
790         g_Map.log("Placing lion statues in the central path");
791         const statueCount = scaleByMapSize(10, 40);
792         const centralPathStart = cityGridPosition[0][gridPointXCenter];
793         const centralPathLength = centralPathStart.distanceTo(
794                 cityGridPosition[gridPointsY - 1][gridPointXCenter]);
795         const centralPathAngle = cityGridAngle[0][gridPointXCenter];
796         for (let i = 0; i < 2; ++i)
797                 for (let stat = 0; stat < statueCount; ++stat)
798                 {
799                         const start = new Vector2D(0, pathWidthCenter * 3/4 * (i - 0.5))
800                                 .rotate(centralPathAngle)
801                                 .add(centralPathStart);
802                         const position = new Vector2D(centralPathLength, 0)
803                                 .mult(stat / statueCount)
804                                 .rotate(-centralPathAngle)
805                                 .add(start)
806                                 .add(new Vector2D(0.5, 0.5));
808                         if (!avoidClasses(clPathCrossing, 2).allows(position))
809                                 continue;
811                         g_Map.placeEntityPassable(pickRandom(aStatues), 0, position,
812                                 centralPathAngle - Math.PI * (i + 0.5));
813                         clPathStatues.add(position.round());
814                 }
816         g_Map.log("Placing guardian infantry in the central path");
817         const centralChampionsCount = scaleByMapSize(2, 40);
818         for (let i = 0; i < 2; ++i)
819                 for (let champ = 0; champ < centralChampionsCount; ++champ)
820                 {
821                         const start = new Vector2D(0, pathWidthCenter * 1/2 * (i - 0.5))
822                                 .rotate(-centralPathAngle)
823                                 .add(centralPathStart);
824                         const position = new Vector2D(centralPathLength, 0)
825                                 .mult(champ / centralChampionsCount)
826                                 .rotate(-centralPathAngle)
827                                 .add(start)
828                                 .add(new Vector2D(0.5, 0.5));
830                         if (!avoidClasses(clPathCrossing, 2).allows(position))
831                                 continue;
833                         g_Map.placeEntityPassable(pickRandom(oKushChampions), 0, position,
834                                 centralPathAngle - Math.PI * (i - 0.5));
835                         clPathStatues.add(position.round());
836                 }
838         g_Map.log("Placing kushite statues in the secondary paths");
839         for (const x of [gridPointXCenter - 1, gridPointXCenter + 1])
840         {
841                 g_Map.placeEntityAnywhere(aStatueKush, 0, cityGridPosition[gridPointYCenter][x],
842                         cityGridAngle[gridPointYCenter][x]);
843                 clPathStatues.add(cityGridPosition[gridPointYCenter][x]);
844         }
846         g_Map.log("Creating ritual place near the wonder");
847         const ritualPosition = Vector2D.average([
848                 templePosition[Math.floor(templePosition.length / 2) - 1],
849                 templePosition[Math.ceil(templePosition.length / 2) - 1],
850                 cityGridPosition[0][gridPointXCenter],
851                 cityGridPosition[0][gridPointXCenter - 1]
852         ]).round();
854         const ritualAngle =
855                 (cityGridAngle[0][gridPointXCenter] + cityGridAngle[0][gridPointXCenter - 1]) / 2 + Math.PI / 2;
857         g_Map.placeEntityPassable(aStatueKush, 0, ritualPosition, ritualAngle - Math.PI / 2);
859         createArea(
860                 new DiskPlacer(scaleByMapSize(4, 6), ritualPosition),
861                 [
862                         new LayeredPainter([tPathWild, tPath], [1]),
863                         new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetPath, 2),
864                         new TileClassPainter(clRitualPlace)
865                 ],
866                 avoidClasses(clCliff, 1));
868         createArea(
869                 new DiskPlacer(0, new Vector2D(-1, -1).add(ritualPosition)),
870                 new ElevationPainter(heightDesert + heightOffsetStatue));
872         g_Map.log("Placing healers at the ritual place");
873         const [healerPosition, healerAngle] = distributePointsOnCircularSegment(
874                 scaleByMapSize(2, 10), Math.PI, ritualAngle, scaleByMapSize(2, 3), ritualPosition);
875         for (let i = 0; i < healerPosition.length; ++i)
876                 g_Map.placeEntityPassable(oKushHealer, 0, healerPosition[i], healerAngle[i] + Math.PI);
878         g_Map.log("Placing statues at the ritual place");
879         const [statuePosition, statueAngle] = distributePointsOnCircularSegment(
880                 scaleByMapSize(4, 8), Math.PI, ritualAngle, scaleByMapSize(3, 4), ritualPosition);
881         for (let i = 0; i < statuePosition.length; ++i)
882                 g_Map.placeEntityPassable(pickRandom(aStatues), 0, statuePosition[i], statueAngle[i] + Math.PI);
884         g_Map.log("Placing palms at the ritual place");
885         const palmPosition = distributePointsOnCircularSegment(
886                 scaleByMapSize(6, 16), Math.PI, ritualAngle, scaleByMapSize(4, 5), ritualPosition)[0];
887         for (let i = 0; i < palmPosition.length; ++i)
888                 if (avoidClasses(clTemple, 1).allows(palmPosition[i]))
889                         g_Map.placeEntityPassable(oPalmPath, 0, palmPosition[i], randomAngle());
891         g_Map.log("Painting city paths");
892         const areaPaths = createArea(
893                 new MapBoundsPlacer(),
894                 [
895                         new LayeredPainter([tPathWild, tPath], [1]),
896                         new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetPath, 1)
897                 ],
898                 stayClasses(clPath, 0));
900         g_Map.log("Placing triggerpoints on city paths");
901         createObjectGroupsByAreas(
902                 new SimpleGroup([new SimpleObject(oTriggerPointCityPath, 1, 1, 0, 0)], true,
903                         clTriggerPointCityPath),
904                 0,
905                 [avoidClasses(clTriggerPointCityPath, 8), stayClasses(clPathCrossing, 2)],
906                 scaleByMapSize(20, 100),
907                 30,
908                 [areaPaths]);
910         g_Map.log("Placing city districts");
911         for (let y = 1; y < gridPointsY; ++y)
912                 for (let x = 1; x < gridPointsX; ++x)
913                         createArea(
914                                 new ConvexPolygonPlacer(
915                                         [
916                                                 cityGridPosition[y - 1][x - 1],
917                                                 cityGridPosition[y - 1][x],
918                                                 cityGridPosition[y][x - 1],
919                                                 cityGridPosition[y][x]
920                                         ], Infinity),
921                                 [
922                                         new TerrainPainter(tRoadDesert),
923                                         new CityPainter(layoutKushCity,
924                                                 (-cityGridAngle[y][x - 1] - cityGridAngle[y][x]) / 2, 0),
925                                         new TileClassPainter(clCity)
926                                 ],
927                                 new StaticConstraint(avoidClasses(clPath, 0)));
929         let entitiesGates;
930         if (placeNapataWall)
931         {
932                 g_Map.log("Placing front walls");
933                 const wallGridMaxAngleSummand = scaleByMapSize(0.04, 0.03) * Math.PI;
934                 const wallGridStartAngle = gridStartAngle - wallGridMaxAngleSummand / 2;
935                 const wallGridRadiusFront = gridRadius(gridPointsY - 1) + pathWidth - 1;
936                 const wallGridMaxAngleFront = gridMaxAngle + wallGridMaxAngleSummand;
937                 let entitiesWalls = placeCircularWall(
938                         gridCenter,
939                         wallGridRadiusFront,
940                         ["tower", "short", "tower", "gate", "tower", "medium", "tower", "short"],
941                         placeNapataWall,
942                         0,
943                         wallGridStartAngle,
944                         wallGridMaxAngleFront,
945                         true,
946                         0,
947                         0);
949                 g_Map.log("Placing side and back walls");
950                 const wallGridRadiusBack = hillRadius - scaleByMapSize(15, 25);
951                 const wallGridMaxAngleBack = gridMaxAngle + wallGridMaxAngleSummand;
952                 const wallGridPositionFront =
953                         distributePointsOnCircularSegment(gridPointsX, wallGridMaxAngleBack, wallGridStartAngle,
954                                 wallGridRadiusFront, gridCenter)[0];
955                 const wallGridPositionBack =
956                         distributePointsOnCircularSegment(gridPointsX, wallGridMaxAngleBack, wallGridStartAngle,
957                                 wallGridRadiusBack, gridCenter)[0];
958                 const wallGridPosition =
959                         [
960                                 wallGridPositionFront[0],
961                                 ...wallGridPositionBack,
962                                 wallGridPositionFront[wallGridPositionFront.length - 1]
963                         ];
964                 for (let x = 1; x < wallGridPosition.length; ++x)
965                         entitiesWalls = entitiesWalls.concat(
966                                 placeLinearWall(
967                                         wallGridPosition[x - 1],
968                                         wallGridPosition[x],
969                                         ["tower", "gate", "tower", "short", "tower", "short", "tower"],
970                                         placeNapataWall,
971                                         0,
972                                         false,
973                                         avoidClasses(clHill, 0, clTemple, 0)));
975                 g_Map.log("Marking walls");
976                 createArea(
977                         new EntitiesObstructionPlacer(entitiesWalls, 0, Infinity),
978                         new TileClassPainter(clWall));
980                 g_Map.log("Marking gates");
981                 entitiesGates = entitiesWalls.filter(entity => entity.templateName.endsWith(oWallGate));
982                 createArea(
983                         new EntitiesObstructionPlacer(entitiesGates, 0, Infinity),
984                         new TileClassPainter(clGate));
986                 g_Map.log("Painting wall terrain");
987                 createArea(
988                         new MapBoundsPlacer(),
989                         [
990                                 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetWalls, 2),
991                                 new TerrainPainter(tPathWild)
992                         ],
993                         [
994                                 new NearTileClassConstraint(clWall, 1),
995                                 avoidClasses(clCliff, 0)
996                         ]);
998                 g_Map.log("Painting gate terrain");
999                 for (const entity of entitiesGates)
1000                         createArea(
1001                                 new DiskPlacer(pathWidth, entity.GetPosition2D()),
1002                                 [
1003                                         new LayeredPainter([tPathWild, tPath], [1]),
1004                                         new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetPath, 2),
1005                                 ],
1006                                 [
1007                                         avoidClasses(clCliff, 0, clPath, 0, clCity, 0),
1008                                         new NearTileClassConstraint(clPath, pathWidth + 1)
1009                                 ]);
1010         }
1011         yield 70;
1013         g_Map.log("Finding road starting points");
1014         const roadStartLocations = shuffleArray(
1015                 entitiesGates ?
1016                         entitiesGates.map(entity => entity.GetPosition2D()) :
1017                         [
1018                                 ...cityGridPosition.map(gridPos => gridPos[0]),
1019                                 ...cityGridPosition.map(gridPos => gridPos[gridPos.length - 1]),
1020                                 ...cityGridPosition[cityGridPosition.length - 1]
1021                         ]);
1023         g_Map.log("Finding possible roads");
1024         let roadConstraint = new StaticConstraint(
1025                 [
1026                         stayDesert,
1027                         avoidClasses(clHill, 0, clCity, 0, clPyramid, 6, clPlayer, 16)
1028                 ]);
1030         const areaCityPaths =
1031                 new Area(areasCityPaths.reduce((points, area) => points.concat(area.getPoints()), []));
1032         const areaRoads = [];
1033         for (const roadStart of roadStartLocations)
1034         {
1035                 if (areaRoads.length >= scaleByMapSize(2, 5))
1036                         break;
1038                 const closestPoint = areaCityPaths.getClosestPointTo(roadStart);
1039                 roadConstraint = new StaticConstraint([roadConstraint, avoidClasses(clRoad, 20)]);
1040                 for (let tries = 0; tries < 30; ++tries)
1041                 {
1042                         const area = createArea(
1043                                 new PathPlacer(
1044                                         Vector2D.add(closestPoint,
1045                                                 new Vector2D(0, 3/4 * mapSize).rotate(closestPoint.angleTo(roadStart))),
1046                                         roadStart,
1047                                         scaleByMapSize(5, 3),
1048                                         0.1,
1049                                         5,
1050                                         0.5,
1051                                         0,
1052                                         0),
1053                                 new TileClassPainter(clRoad),
1054                                 roadConstraint);
1056                         if (area && area.getPoints().length)
1057                         {
1058                                 areaRoads.push(area);
1059                                 break;
1060                         }
1061                 }
1062         }
1064         g_Map.log("Painting roads");
1065         createArea(
1066                 new MapBoundsPlacer(),
1067                 [
1068                         new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetRoad, 1),
1069                         new LayeredPainter([tPathWild, tPath], [1]),
1070                 ],
1071                 [stayClasses(clRoad, 0), avoidClasses(clPath, 0)]);
1073         g_Map.log("Marking road palm area");
1074         const areaRoadPalms = createArea(
1075                 new MapBoundsPlacer(),
1076                 undefined,
1077                 [
1078                         new NearTileClassConstraint(clRoad, 1),
1079                         avoidClasses(clRoad, 0, clPath, 1, clWall, 4, clGate, 4)
1080                 ]);
1082         if (areaRoadPalms && areaRoadPalms.getPoints().length)
1083         {
1084                 g_Map.log("Placing road palms");
1085                 createObjectGroupsByAreas(
1086                         new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
1087                         0,
1088                         avoidClasses(clForest, 2, clGate, 7),
1089                         scaleByMapSize(40, 250),
1090                         20,
1091                         [areaRoadPalms]);
1093                 g_Map.log("Placing road bushes");
1094                 createObjectGroupsByAreas(
1095                         new SimpleGroup([new RandomObject(aBushesCity, 1, 1, 0, 0)], true, clForest),
1096                         0,
1097                         avoidClasses(clForest, 1),
1098                         scaleByMapSize(40, 200),
1099                         20,
1100                         [areaRoadPalms]);
1101         }
1102         yield 75;
1104         g_Map.log("Marking city bush area");
1105         const areaCityBushes =
1106                 createArea(
1107                         new MapBoundsPlacer(),
1108                         undefined,
1109                         [
1110                                 new NearTileClassConstraint(clPath, 1),
1111                                 avoidClasses(
1112                                         clPath, 0,
1113                                         clRoad, 0,
1114                                         clPyramid, 20,
1115                                         clRitualPlace, 8,
1116                                         clTemple, 3,
1117                                         clWall, 3,
1118                                         clTower, 1,
1119                                         clFortress, 1,
1120                                         clHouse, 1,
1121                                         clForge, 1,
1122                                         clElephantStable, 1,
1123                                         clStable, 1,
1124                                         clCivicCenter, 1,
1125                                         clBarracks, 1,
1126                                         clBlemmyeCamp, 1,
1127                                         clNobaCamp, 1,
1128                                         clMarket, 1)
1129                         ]);
1131         g_Map.log("Marking city palm area");
1132         const areaCityPalms =
1133                 createArea(
1134                         new MapBoundsPlacer(),
1135                         undefined,
1136                         [
1137                                 new StayAreasConstraint([areaCityBushes]),
1138                                 avoidClasses(clElephantStable, 3)
1139                         ]);
1141         g_Map.log("Placing city palms");
1142         createObjectGroupsByAreas(
1143                 new SimpleGroup([new SimpleObject(aPalmPath, 1, 1, 0, 0)], true, clForest),
1144                 0,
1145                 avoidClasses(clForest, 3),
1146                 scaleByMapSize(40, 400),
1147                 15,
1148                 [areaCityPalms]);
1150         g_Map.log("Placing city bushes");
1151         createObjectGroupsByAreas(
1152                 new SimpleGroup([new RandomObject(aBushesCity, 1, 1, 0, 0)], true, clForest),
1153                 0,
1154                 avoidClasses(clForest, 1),
1155                 scaleByMapSize(20, 200),
1156                 15,
1157                 [areaCityBushes]);
1159         if (placeNapataWall)
1160         {
1161                 g_Map.log("Marking wall palm area");
1162                 const areaWallPalms = createArea(
1163                         new MapBoundsPlacer(),
1164                         undefined,
1165                         new StaticConstraint([
1166                                 new NearTileClassConstraint(clWall, 2),
1167                                 avoidClasses(clPath, 1, clRoad, 1, clWall, 1, clGate, 3, clTemple, 2, clHill, 6)
1168                         ]));
1170                 g_Map.log("Placing wall palms");
1171                 createObjectGroupsByAreas(
1172                         new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
1173                         0,
1174                         avoidClasses(clForest, 2),
1175                         scaleByMapSize(40, 250),
1176                         50,
1177                         [areaWallPalms]);
1178         }
1180         createBumps(
1181                 new StaticConstraint(avoidClasses(
1182                         clPlayer, 6,
1183                         clCity, 0,
1184                         clWater, 2,
1185                         clHill, 0,
1186                         clPath, 0,
1187                         clRoad, 0,
1188                         clTemple, 4,
1189                         clPyramid, 8,
1190                         clWall, 0,
1191                         clGate, 4)),
1192                 scaleByMapSize(30, 300),
1193                 1,
1194                 8,
1195                 4,
1196                 0,
1197                 3);
1198         yield 80;
1200         g_Map.log("Setting up common constraints and areas");
1201         const nearWater = new NearTileClassConstraint(clWater, 3);
1202         const avoidCollisionsNomad = new AndConstraint(
1203                 [
1204                         new StaticConstraint(avoidClasses(
1205                                 clCliff, 0, clHill, 0, clPlayer, 15, clWater, 1, clPath, 2, clRitualPlace, 10,
1206                                 clTemple, 4, clPyramid, 7, clCity, 4, clWall, 4, clGate, 8)),
1207                         avoidClasses(clForest, 1, clRock, 4, clMetal, 4, clFood, 2, clSoldier, 1, clTreasure, 1)
1208                 ]);
1210         let avoidCollisions = new AndConstraint(
1211                 [
1212                         avoidCollisionsNomad,
1213                         new StaticConstraint(avoidClasses(clRoad, 6, clFood, 6))
1214                 ]);
1216         const areaDesert = createArea(new MapBoundsPlacer(), undefined, stayDesert);
1217         const areaFertileLand = createArea(new MapBoundsPlacer(), undefined, stayFertileLand);
1219         createForests(
1220                 [tForestFloorFertile, tForestFloorFertile, tForestFloorFertile, pForestPalms, pForestPalms],
1221                 [
1222                         stayFertileLand,
1223                         avoidClasses(clForest, 15),
1224                         new StaticConstraint([avoidClasses(clWater, 2), avoidCollisions])
1225                 ],
1226                 clForest,
1227                 scaleByMapSize(250, 2000));
1229         g_Map.log("Creating mines");
1230         const avoidCollisionsMines = [
1231                 avoidClasses(clRock, 10, clMetal, 10),
1232                 new StaticConstraint(avoidClasses(
1233                         clWater, 4, clCliff, 4, clCity, 4, clRitualPlace, 10, clPlayer, 20, clForest, 4,
1234                         clPyramid, 6, clTemple, 4, clPath, 4, clRoad, 4, clGate, 8))
1235         ];
1237         const mineObjects = (templateSmall, templateLarge) => ({
1238                 "large": [
1239                         new SimpleObject(templateSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1),
1240                         new SimpleObject(templateLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)
1241                 ],
1242                 "small": [
1243                         new SimpleObject(templateSmall, 3, 4, 1, 3, 0, 2 * Math.PI, 1)
1244                 ]
1245         });
1247         const mineObjectsPerBiome = [
1248                 {
1249                         "desert": mineObjects(oMetalSmallDesert, oMetalLargeDesert),
1250                         "fertileLand": mineObjects(oMetalSmallFertileLand, oMetalLargeFertileLand),
1251                         "tileClass": clMetal
1252                 },
1253                 {
1254                         "desert": mineObjects(oStoneSmallDesert, oStoneLargeDesert),
1255                         "fertileLand": mineObjects(oStoneSmallFertileLand, oStoneLargeFertileLand),
1256                         "tileClass": clRock
1257                 }
1258         ];
1260         for (let i = 0; i < scaleByMapSize(6, 22); ++i)
1261         {
1262                 const mineObjectsBiome = pickRandom(mineObjectsPerBiome);
1263                 for (const k in mineObjectsBiome.desert)
1264                         createObjectGroupsByAreas(
1265                                 new SimpleGroup(mineObjectsBiome.desert[k], true, mineObjectsBiome.tileClass),
1266                                 0,
1267                                 avoidCollisionsMines.concat(
1268                                         [avoidClasses(clFertileLand, 12, mineObjectsBiome.tileClass, 15)]),
1269                                 1,
1270                                 60,
1271                                 [areaDesert]);
1272         }
1274         for (let i = 0; i < (isNomad() ? scaleByMapSize(6, 16) : scaleByMapSize(0, 8)); ++i)
1275         {
1276                 const mineObjectsBiome = pickRandom(mineObjectsPerBiome);
1277                 createObjectGroupsByAreas(
1278                         new SimpleGroup(mineObjectsBiome.fertileLand.small, true, mineObjectsBiome.tileClass),
1279                         0,
1280                         avoidCollisionsMines.concat([
1281                                 avoidClasses(clDesert, 5, clMetal, 15, clRock, 15, mineObjectsBiome.tileClass, 20)
1282                         ]),
1283                         1,
1284                         80,
1285                         [areaFertileLand]);
1286         }
1288         g_Map.log("Placing triggerpoints for attackers");
1289         createObjectGroups(
1290                 new SimpleGroup([new SimpleObject(oTriggerPointAttackerPatrol, 1, 1, 0, 0)], true,
1291                         clTriggerPointMap),
1292                 0,
1293                 [avoidClasses(
1294                         clCity, 8,
1295                         clCliff, 4,
1296                         clHill, 4,
1297                         clWater, 0,
1298                         clWall, 2,
1299                         clForest, 1,
1300                         clRock, 4,
1301                         clMetal, 4,
1302                         clTriggerPointMap, 15)],
1303                 scaleByMapSize(20, 100),
1304                 30);
1306         g_Map.log("Creating berries");
1307         createObjectGroupsByAreas(
1308                 new SimpleGroup([new SimpleObject(oBerryBushGrapes, 4, 6, 1, 2)], true, clFood),
1309                 0,
1310                 avoidCollisions,
1311                 scaleByMapSize(3, 15),
1312                 50,
1313                 [areaFertileLand]);
1315         g_Map.log("Creating rhinos");
1316         createObjectGroupsByAreas(
1317                 new SimpleGroup([new SimpleObject(oRhino, 1, 1, 0, 1)], true, clFood),
1318                 0,
1319                 avoidCollisions,
1320                 scaleByMapSize(2, 10),
1321                 50,
1322                 [areaDesert]);
1324         g_Map.log("Creating warthogs");
1325         createObjectGroupsByAreas(
1326                 new SimpleGroup([new SimpleObject(oWarthog, 1, 1, 0, 1)], true, clFood),
1327                 0,
1328                 avoidCollisions,
1329                 scaleByMapSize(2, 10),
1330                 50,
1331                 [areaFertileLand]);
1333         g_Map.log("Creating giraffes");
1334         createObjectGroups(
1335                 new SimpleGroup(
1336                         [
1337                                 new SimpleObject(oGiraffe, 2, 3, 2, 4),
1338                                 new SimpleObject(oGiraffeInfant, 2, 3, 2, 4)
1339                         ], true, clFood),
1340                 0,
1341                 avoidCollisions,
1342                 scaleByMapSize(2, 10),
1343                 50);
1345         g_Map.log("Creating gazelles");
1346         createObjectGroups(
1347                 new SimpleGroup([new SimpleObject(oGazelle, 5, 7, 2, 4)], true, clFood),
1348                 0,
1349                 avoidCollisions,
1350                 scaleByMapSize(2, 10),
1351                 50,
1352                 [areaDesert]);
1354         if (!isNomad())
1355         {
1356                 g_Map.log("Creating lions");
1357                 createObjectGroupsByAreas(
1358                         new SimpleGroup(
1359                                 [
1360                                         new SimpleObject(oLion, 1, 2, 2, 4),
1361                                         new SimpleObject(oLioness, 2, 3, 2, 4)
1362                                 ], true, clFood),
1363                         0,
1364                         [avoidCollisions, avoidClasses(clPlayer, 20)],
1365                         scaleByMapSize(2, 10),
1366                         50,
1367                         [areaDesert]);
1368         }
1370         g_Map.log("Creating elephants");
1371         createObjectGroupsByAreas(
1372                 new SimpleGroup(
1373                         [
1374                                 new SimpleObject(oElephant, 2, 3, 2, 4),
1375                                 new SimpleObject(oElephantInfant, 2, 3, 2, 4)
1376                         ], true, clFood),
1377                 0,
1378                 avoidCollisions,
1379                 scaleByMapSize(2, 10),
1380                 50,
1381                 [areaDesert]);
1383         g_Map.log("Creating crocodiles");
1384         if (!isNomad())
1385                 createObjectGroupsByAreas(
1386                         new SimpleGroup([new SimpleObject(oCrocodile, 2, 3, 3, 5)], true, clFood),
1387                         0,
1388                         [nearWater, avoidCollisions],
1389                         scaleByMapSize(1, 6),
1390                         50,
1391                         [areaFertileLand]);
1393         yield 85;
1395         g_Map.log("Marking irrigation canal tree area");
1396         const areaIrrigationCanalTrees = createArea(
1397                 new MapBoundsPlacer(),
1398                 undefined,
1399                 [
1400                         nearWater,
1401                         avoidClasses(clPassage, 3),
1402                         avoidCollisions
1403                 ]);
1405         g_Map.log("Creating irrigation canal trees");
1406         createObjectGroupsByAreas(
1407                 new SimpleGroup([new RandomObject(oPalms, 1, 1, 1, 1)], true, clForest),
1408                 0,
1409                 avoidClasses(clForest, 1),
1410                 scaleByMapSize(100, 600),
1411                 50,
1412                 [areaIrrigationCanalTrees]);
1414         createStragglerTrees(
1415                 oPalms,
1416                 [stayFertileLand, avoidCollisions],
1417                 clForest,
1418                 scaleByMapSize(50, 400),
1419                 200);
1421         createStragglerTrees(
1422                 [oAcacia],
1423                 [stayDesert, avoidCollisions],
1424                 clForest,
1425                 scaleByMapSize(50, 400),
1426                 200);
1428         g_Map.log("Placing archer groups on the hilltop");
1429         createObjectGroupsByAreas(
1430                 new SimpleGroup([new RandomObject([oKushCitizenArcher, oKushChampionArcher],
1431                         scaleByMapSize(4, 10), scaleByMapSize(6, 20), 1, 4)], true, clSoldier),
1432                 0,
1433                 new StaticConstraint([avoidClasses(clCliff, 1), new NearTileClassConstraint(clCliff, 5)]),
1434                 scaleByMapSize(1, 5) / 3 * getDifficulty(),
1435                 250,
1436                 [areaHilltop]);
1438         g_Map.log("Placing individual archers on the hill");
1439         createObjectGroupsByAreas(
1440                 new SimpleGroup([new RandomObject([oKushCitizenArcher, oKushChampionArcher], 1, 1, 1, 3)], true,
1441                         clSoldier),
1442                 0,
1443                 new StaticConstraint([
1444                         new HeightConstraint(heightHillArchers, heightHilltop),
1445                         avoidClasses(clCliff, 1, clSoldier, 1),
1446                         new NearTileClassConstraint(clCliff, 5)
1447                 ]),
1448                 scaleByMapSize(8, 100) / 3 * getDifficulty(),
1449                 250,
1450                 [areaHill]);
1452         g_Map.log("Placing siege engines on the hilltop");
1453         createObjectGroupsByAreas(
1454                 new SimpleGroup([new RandomObject(oPtolSiege, 1, 1, 1, 3)], true, clSoldier),
1455                 0,
1456                 new StaticConstraint(
1457                         [
1458                                 new NearTileClassConstraint(clCliff, 5),
1459                                 avoidClasses(clCliff, 1, clSoldier, 1)
1460                         ]),
1461                 scaleByMapSize(1, 6) / 3 * getDifficulty(),
1462                 250,
1463                 [areaHilltop]);
1465         const avoidCollisionsPyramids = new StaticConstraint(
1466                 [
1467                         avoidCollisions,
1468                         new NearTileClassConstraint(clPyramid, 10)
1469                 ]);
1470         if (!isNomad())
1471         {
1472                 g_Map.log("Placing soldiers near pyramids");
1473                 createObjectGroupsByAreas(
1474                         new SimpleGroup([new SimpleObject(oKushCitizenArcher, 1, 1, 1, 1)], true, clSoldier),
1475                         0,
1476                         avoidCollisionsPyramids,
1477                         scaleByMapSize(3, 8),
1478                         250,
1479                         [areaPyramids]);
1481                 g_Map.log("Placing treasures at the pyramid");
1482                 createObjectGroupsByAreas(
1483                         new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
1484                         0,
1485                         avoidCollisionsPyramids,
1486                         scaleByMapSize(1, 10),
1487                         250,
1488                         [areaPyramids]);
1489         }
1491         g_Map.log("Placing treasures on the hilltop");
1492         createObjectGroupsByAreas(
1493                 new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
1494                 0,
1495                 avoidClasses(clCliff, 1, clTreasure, 1),
1496                 scaleByMapSize(8, 35),
1497                 250,
1498                 [areaHilltop]);
1500         g_Map.log("Placing treasures in the city");
1501         const pathBorderConstraint = new AndConstraint([
1502                 new StaticConstraint([new NearTileClassConstraint(clCity, 1)]),
1503                 avoidClasses(clTreasure, 2, clStatue, 10, clPathStatues, 4, clWall, 2, clForest, 1)
1504         ]);
1505         createObjectGroupsByAreas(
1506                 new SimpleGroup([new RandomObject(oTreasuresCity, 1, 1, 0, 2)], true, clTreasure),
1507                 0,
1508                 pathBorderConstraint,
1509                 scaleByMapSize(2, 60),
1510                 500,
1511                 [areaPaths]);
1513         g_Map.log("Placing handcarts on the paths");
1514         createObjectGroupsByAreas(
1515                 new SimpleGroup([new SimpleObject(aHandcart, 1, 1, 1, 1)], true, clDecorative),
1516                 0,
1517                 [pathBorderConstraint, avoidClasses(clDecorative, 10)],
1518                 scaleByMapSize(0, 5),
1519                 250,
1520                 [areaPaths]);
1522         g_Map.log("Placing fence in fertile land");
1523         createObjectGroupsByAreas(
1524                 new SimpleGroup([new SimpleObject(aPlotFence, 1, 1, 1, 1)], true, clDecorative),
1525                 0,
1526                 new StaticConstraint([avoidCollisions, avoidClasses(clWater, 6, clDecorative, 10)]),
1527                 scaleByMapSize(1, 10),
1528                 250,
1529                 [areaFertileLand]);
1531         g_Map.log("Creating fish");
1532         createObjectGroups(
1533                 new SimpleGroup([new SimpleObject(oFish, 3, 4, 2, 3)], true, clFood),
1534                 0,
1535                 [new StaticConstraint(stayClasses(clWater, 6)), avoidClasses(clFood, 12)],
1536                 scaleByMapSize(20, 120),
1537                 50);
1539         yield 95;
1541         avoidCollisions = new StaticConstraint(avoidCollisions);
1543         createDecoration(
1544                 aBushesDesert.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
1545                 aBushesDesert.map(bush => scaleByMapSize(20, 120) * randIntInclusive(1, 3)),
1546                 [stayDesert, avoidCollisions]);
1548         createDecoration(
1549                 aBushesFertileLand.map(bush => [new SimpleObject(bush, 0, 4, 2, 4)]),
1550                 aBushesFertileLand.map(bush => scaleByMapSize(20, 120) * randIntInclusive(1, 3)),
1551                 [stayFertileLand, avoidCollisions]);
1553         createDecoration(
1554                 [[new SimpleObject(aRock, 0, 4, 2, 4)]],
1555                 [[scaleByMapSize(80, 500)]],
1556                 [stayDesert, avoidCollisions]);
1558         createDecoration(
1559                 aBushesFertileLand.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
1560                 aBushesFertileLand.map(bush => scaleByMapSize(100, 800)),
1561                 [new HeightConstraint(heightWaterLevel, heightShoreline), avoidCollisions]);
1563         g_Map.log("Creating reeds");
1564         createObjectGroupsByAreas(
1565                 new SimpleGroup([new RandomObject(aWaterDecoratives, 2, 4, 1, 2)], true),
1566                 0,
1567                 new StaticConstraint(new NearTileClassConstraint(clFertileLand, 4)),
1568                 scaleByMapSize(50, 400),
1569                 20,
1570                 [areaWater]);
1572         g_Map.log("Creating reeds at the irrigation canals");
1573         for (const area of areasPassages)
1574                 createObjectGroupsByAreas(
1575                         new SimpleGroup([new RandomObject(aWaterDecoratives, 2, 4, 1, 2)], true),
1576                         0,
1577                         undefined,
1578                         15,
1579                         20,
1580                         [area]);
1582         g_Map.log("Creating hawk");
1583         for (let i = 0; i < scaleByMapSize(0, 2); ++i)
1584                 g_Map.placeEntityAnywhere(oHawk, 0, mapCenter, randomAngle());
1586         placePlayersNomad(clPlayer,
1587                 [
1588                         avoidClasses(clHill, 15, clSoldier, 20, clCity, 15, clWall, 20),
1589                         avoidCollisionsNomad
1590                 ]);
1592         setWindAngle(-0.43);
1593         setWaterHeight(heightWaterLevel + SEA_LEVEL);
1594         setWaterTint(0.161, 0.286, 0.353);
1595         setWaterColor(0.129, 0.176, 0.259);
1596         setWaterWaviness(8);
1597         setWaterMurkiness(0.87);
1598         setWaterType("lake");
1600         setAmbientColor(0.58, 0.443, 0.353);
1602         setSunColor(0.733, 0.746, 0.574);
1603         setSunRotation(Math.PI / 2 * randFloat(-1, 1));
1604         setSunElevation(Math.PI / 7);
1606         setFogFactor(0);
1607         setFogThickness(0);
1608         setFogColor(0.69, 0.616, 0.541);
1610         setPPEffect("hdr");
1611         setPPContrast(0.67);
1612         setPPSaturation(0.42);
1613         setPPBloom(0.23);
1615         return g_Map;