Add a difficulty setting for Jebel Barkal, refs #4963.
[0ad.git] / binaries / data / mods / public / maps / random / jebel_barkal.js
blob7ddb10d911db9d661e7b0681014c79e1ec98cbfc
1 /**
2  * For historic reference, see http://www.jebelbarkal.org/images/maps/siteplan.jpg
3  */
5 Engine.LoadLibrary("rmgen");
6 Engine.LoadLibrary("rmgen-common");
7 Engine.LoadLibrary("heightmap");
9 TILE_CENTERED_HEIGHT_MAP = true;
11 const tSand = "desert_sand_dunes_100";
12 const tHilltop = ["new_savanna_dirt_c", "new_savanna_dirt_d"];
13 const tHillGround = ["savanna_dirt_rocks_a", "savanna_dirt_rocks_b", "savanna_dirt_rocks_c"];
14 const tHillCliff = ["savanna_cliff_a_red", "savanna_cliff_b_red"];
15 const tRoadDesert = "savanna_tile_a";
16 const tRoadFertileLand = "savanna_tile_a";
17 const tWater = "desert_sand_wet";
18 const tGrass = ["savanna_shrubs_a_wetseason", "alpine_grass_b_wild", "medit_shrubs_a", "steppe_grass_green_a"];
19 const tForestFloorFertile = pickRandom(tGrass);
20 const tGrassTransition1 = "desert_grass_a";
21 const tGrassTransition2 = "steppe_grass_dirt_66";
22 const tPath = "road2";
23 const tPathWild = "road_med";
25 const oAcacia = "gaia/flora_tree_acacia";
26 const oPalmPath = "gaia/flora_tree_cretan_date_palm_tall";
27 const oPalms = [
28         "gaia/flora_tree_cretan_date_palm_tall",
29         "gaia/flora_tree_cretan_date_palm_short",
30         "gaia/flora_tree_palm_tropic",
31         "gaia/flora_tree_date_palm",
32         "gaia/flora_tree_senegal_date_palm",
33         "gaia/flora_tree_medit_fan_palm"
35 const oBerryBushGrapes = "gaia/flora_bush_grapes";
36 const oBerryBushDesert = "gaia/flora_bush_berry_desert";
37 const oStoneLarge = "gaia/geology_stonemine_desert_quarry";
38 const oStoneSmall = "gaia/geology_stone_desert_small";
39 const oMetalLarge = "gaia/geology_metal_desert_slabs";
40 const oMetalSmall = "gaia/geology_metal_desert_small";
41 const oFoodTreasureBin = "gaia/treasure/food_bin";
42 const oFoodTreasureCrate = "gaia/treasure/food_crate";
43 const oFoodTreasureJars = "gaia/treasure/food_jars";
44 const oWoodTreasure = "gaia/treasure/wood";
45 const oStoneTreasure = "gaia/treasure/stone";
46 const oMetalTreasure = "gaia/treasure/metal";
47 const oTreasuresHill = [oWoodTreasure, oStoneTreasure, oMetalTreasure];
48 const oTreasuresCity = [oFoodTreasureBin, oFoodTreasureCrate, oFoodTreasureJars].concat(oTreasuresHill);
49 const oGiraffe = "gaia/fauna_giraffe";
50 const oGiraffeInfant = "gaia/fauna_giraffe_infant";
51 const oGazelle = "gaia/fauna_gazelle";
52 const oRhino = "gaia/fauna_rhino";
53 const oWarthog = "gaia/fauna_boar";
54 const oElephant = "gaia/fauna_elephant_african_bush";
55 const oElephantInfant = "gaia/fauna_elephant_african_infant";
56 const oLion = "gaia/fauna_lion";
57 const oLioness = "gaia/fauna_lioness";
58 const oCrocodile = "gaia/fauna_crocodile";
59 const oFish = "gaia/fauna_fish";
60 const oHawk = "gaia/fauna_hawk";
61 const oTempleApedemak = "structures/kush_temple";
62 const oTempleAmun = "structures/kush_temple_amun";
63 const oPyramidLarge = "structures/kush_pyramid_large";
64 const oPyramidSmall = "structures/kush_pyramid_small";
65 const oWonderPtol = "structures/ptol_wonder";
66 const oFortress = "structures/kush_fortress";
67 const oTower = g_MapSettings.Size >= 256 ? "structures/kush_defense_tower" : "structures/kush_sentry_tower";
68 const oHouse = "structures/kush_house";
69 const oMarket = "structures/kush_market";
70 const oBlacksmith = "structures/kush_blacksmith";
71 const oBlemmyeCamp = "structures/kush_blemmye_camp";
72 const oNubaVillage = "structures/kush_nuba_village";
73 const oCivicCenter = "structures/kush_civil_centre";
74 const oBarracks = "structures/kush_barracks";
75 const oStable = "structures/kush_stable";
76 const oElephantStables = "structures/kush_elephant_stables";
77 const oWallMedium = "structures/kush_wall_medium";
78 const oWallGate = "structures/kush_wall_gate";
79 const oWallTower = "structures/kush_wall_tower";
80 const oKushCitizenArcher = "units/kush_infantry_archer_e";
81 const oKushChampionArcher = "units/kush_champion_infantry";
82 const oKushChampions = [
83         oKushChampionArcher,
84         "units/kush_champion_infantry_amun",
85         "units/kush_champion_infantry_apedemak"
87 const oPtolSiege = ["units/ptol_mechanical_siege_lithobolos_unpacked", "units/ptol_mechanical_siege_polybolos_unpacked"];
88 const oTriggerPointCityPath = "trigger/trigger_point_A";
89 const oTriggerPointAttackerPatrol = "trigger/trigger_point_B";
91 const aRock = actorTemplate("geology/stone_savanna_med");
92 const aHandcart = actorTemplate("props/special/eyecandy/handcart_1");
93 const aPlotFence = actorTemplate("props/special/common/plot_fence");
94 const aStatueKush = actorTemplate("props/special/eyecandy/statues_kush");
95 const aStatues = [
96         "props/structures/kushites/statue_pedestal_rectangular",
97         "props/structures/kushites/statue_pedestal_rectangular_lion"
98 ].map(actorTemplate);
99 const aBushesFertileLand = [
100         ...new Array(3).fill("props/flora/shrub_spikes"),
101         ...new Array(3).fill("props/flora/ferns"),
102         "props/flora/shrub_tropic_plant_a",
103         "props/flora/shrub_tropic_plant_b",
104         "props/flora/shrub_tropic_plant_flower",
105         "props/flora/foliagebush",
106         "props/flora/bush",
107         "props/flora/bush_medit_la",
108         "props/flora/bush_medit_la_lush",
109         "props/flora/bush_medit_me_lush",
110         "props/flora/bush_medit_sm",
111         "props/flora/bush_medit_sm_lush",
112         "props/flora/bush_tempe_la_lush"
113 ].map(actorTemplate);
114 const aBushesDesert = [
115         "props/flora/bush_dry_a",
116         "props/flora/bush_medit_la_dry",
117         "props/flora/bush_medit_me_dry",
118         "props/flora/bush_medit_sm",
119         "props/flora/bush_medit_sm_dry",
120         "props/flora/bush_tempe_me_dry",
121         "props/flora/grass_soft_dry_large_tall",
122         "props/flora/grass_soft_dry_small_tall"
123 ].map(actorTemplate);
124 const aWaterDecoratives = ["props/flora/reeds_pond_lush_a"].map(actorTemplate);
126 const pForestPalms = [
127         tForestFloorFertile,
128         ...oPalms.map(tree => tForestFloorFertile + TERRAIN_SEPARATOR + tree),
129         tForestFloorFertile];
131 const heightScale = num => num * g_MapSettings.Size / 320;
133 const minHeightSource = 3;
134 const maxHeightSource = 800;
136 const g_Map = new RandomMap(0, tSand);
137 const mapSize = g_Map.getSize();
138 const mapCenter = g_Map.getCenter();
139 const mapBounds = g_Map.getBounds();
140 const numPlayers = getNumPlayers();
142 const clHill = g_Map.createTileClass();
143 const clCliff = g_Map.createTileClass();
144 const clDesert = g_Map.createTileClass();
145 const clFertileLand = g_Map.createTileClass();
146 const clWater = g_Map.createTileClass();
147 const clIrrigationCanal = g_Map.createTileClass();
148 const clPassage = g_Map.createTileClass();
149 const clPlayer = g_Map.createTileClass();
150 const clBaseResource = g_Map.createTileClass();
151 const clFood = g_Map.createTileClass();
152 const clForest = g_Map.createTileClass();
153 const clRock = g_Map.createTileClass();
154 const clMetal = g_Map.createTileClass();
155 const clTreasure = g_Map.createTileClass();
156 const clCity = g_Map.createTileClass();
157 const clPath = g_Map.createTileClass();
158 const clPathStatues = g_Map.createTileClass();
159 const clPathCrossing = g_Map.createTileClass();
160 const clStatue = g_Map.createTileClass();
161 const clWall = g_Map.createTileClass();
162 const clGate = g_Map.createTileClass();
163 const clTriggerPointCityPath = g_Map.createTileClass();
164 const clTriggerPointMap = g_Map.createTileClass();
165 const clSoldier = g_Map.createTileClass();
166 const clTower = g_Map.createTileClass();
167 const clFortress = g_Map.createTileClass();
168 const clTemple = g_Map.createTileClass();
169 const clPyramid = g_Map.createTileClass();
170 const clHouse = g_Map.createTileClass();
171 const clBlacksmith = g_Map.createTileClass();
172 const clStable = g_Map.createTileClass();
173 const clElephantStables = g_Map.createTileClass();
174 const clCivicCenter = g_Map.createTileClass();
175 const clBarracks = g_Map.createTileClass();
176 const clBlemmyeCamp = g_Map.createTileClass();
177 const clNubaVillage = g_Map.createTileClass();
178 const clMarket = g_Map.createTileClass();
179 const clDecorative = g_Map.createTileClass();
181 const riverAngle = Math.PI * 0.05;
183 const hillRadius = scaleByMapSize(40, 120);
184 const positionPyramids = new Vector2D(fractionToTiles(0.15), fractionToTiles(0.75));
186 const pathWidth = 4;
187 const pathWidthCenter = 10;
188 const pathWidthSecondary = 6;
190 const placeNapataWall = getDifficulty() >= 3;
192 const layoutFertileLandTextures = [
193         {
194                 "left": fractionToTiles(0),
195                 "right": fractionToTiles(0.04),
196                 "terrain": createTerrain(tGrassTransition1),
197                 "tileClass": clFertileLand
198         },
199         {
200                 "left": fractionToTiles(0.04),
201                 "right": fractionToTiles(0.08),
202                 "terrain": createTerrain(tGrassTransition2),
203                 "tileClass": clDesert
204         }
207 var layoutKushTemples = [
208         ...new Array(2).fill(0).map((v, i) =>
209         ({
210                 "template": oTempleApedemak,
211                 "pathOffset": new Vector2D(0, 9),
212                 "minMapSize": i == 0 ? 320 : 0
213         })),
214         {
215                 "template": oTempleAmun,
216                 "pathOffset": new Vector2D(0, 12),
217                 "minMapSize": 256
218         },
219         {
220                 "template": oWonderPtol,
221                 "pathOffset": new Vector2D(0,  scaleByMapSize(9, 14)),
222                 "minMapSize": 0
223         },
224         {
225                 "template": oTempleAmun,
226                 "pathOffset": new Vector2D(0, 12),
227                 "minMapSize": 256
228         },
229         ...new Array(2).fill(0).map((v, i) =>
230         ({
231                 "template": oTempleApedemak,
232                 "pathOffset": new Vector2D(0, 9),
233                 "minMapSize": i == 0 ? 0 : 320
234         }))
235 ].filter(temple => mapSize >= temple.minMapSize);
238  * The buildings are set as uncapturable, otherwise the player would gain the buildings via root territory and can delete them without effort.
239  * Keep the entire city uncapturable as a consistent property of the city.
240  */
241 const layoutKushCity = [
242         {
243                 "templateName": "uncapturable|" + oHouse,
244                 "difficulty": "Very Easy",
245                 "painters": new TileClassPainter(clHouse)
246         },
247         {
248                 "templateName": "uncapturable|" + oFortress,
249                 "difficulty": "Medium",
250                 "constraints": [avoidClasses(clFortress, 25), new NearTileClassConstraint(clPath, 8)],
251                 "painters": new TileClassPainter(clFortress)
252         },
253         {
254                 "templateName": "uncapturable|" + oCivicCenter,
255                 "difficulty": "Easy",
256                 "constraints": [avoidClasses(clCivicCenter, 60), new NearTileClassConstraint(clPath, 8)],
257                 "painters": new TileClassPainter(clCivicCenter)
258         },
259         {
260                 "templateName": "uncapturable|" + oElephantStables,
261                 "difficulty": "Medium",
262                 "constraints": avoidClasses(clElephantStables, 10),
263                 "painters": new TileClassPainter(clElephantStables)
264         },
265         {
266                 "templateName": "uncapturable|" + oStable,
267                 "difficulty": "Easy",
268                 "constraints": avoidClasses(clStable, 20),
269                 "painters": new TileClassPainter(clStable)
270         },
271         {
272                 "templateName": "uncapturable|" + oBarracks,
273                 "difficulty": "Easy",
274                 "constraints": avoidClasses(clBarracks, 12),
275                 "painters": new TileClassPainter(clBarracks)
276         },
277         {
278                 "templateName": "uncapturable|" + oTower,
279                 "difficulty": "Medium",
280                 "constraints": avoidClasses(clTower, 17),
281                 "painters": new TileClassPainter(clTower)
282         },
283         {
284                 "templateName": "uncapturable|" + oMarket,
285                 "difficulty": "Very Easy",
286                 "constraints": avoidClasses(clMarket, 15),
287                 "painters": new TileClassPainter(clMarket)
288         },
289         {
290                 "templateName": "uncapturable|" + oBlacksmith,
291                 "difficulty": "Very Easy",
292                 "constraints": avoidClasses(clBlacksmith, 30),
293                 "painters": new TileClassPainter(clBlacksmith)
294         },
295         {
296                 "templateName": "uncapturable|" + oNubaVillage,
297                 "difficulty": "Easy",
298                 "constraints": avoidClasses(clNubaVillage, 30),
299                 "painters": new TileClassPainter(clNubaVillage)
300         },
301         {
302                 "templateName": "uncapturable|" + oBlemmyeCamp,
303                 "difficulty": "Easy",
304                 "constraints": avoidClasses(clBlemmyeCamp, 30),
305                 "painters": new TileClassPainter(clBlemmyeCamp)
306         }
307 ].filter(building => getDifficulty() >= getDifficulties().find(difficulty => difficulty.Name == building.difficulty).Difficulty);
309 g_WallStyles.napata = {
310         "short": readyWallElement("uncapturable|" + oWallMedium),
311         "medium": readyWallElement("uncapturable|" + oWallMedium),
312         "tower": readyWallElement("uncapturable|" + oWallTower),
313         "gate": readyWallElement("uncapturable|" + oWallGate),
314         "overlap": 0.05
317 Engine.SetProgress(10);
319 g_Map.log("Loading hill heightmap");
320 createArea(
321         new MapBoundsPlacer(),
322         new HeightmapPainter(
323                 translateHeightmap(
324                         new Vector2D(-12, scaleByMapSize(-12, -25)),
325                         undefined,
326                         convertHeightmap1Dto2D(Engine.LoadMapTerrain("maps/random/jebel_barkal.pmp").height)),
327                 minHeightSource,
328                 maxHeightSource));
330 const heightDesert = g_Map.getHeight(mapCenter);
331 const heightFertileLand = heightDesert - heightScale(2);
332 const heightShoreline = heightFertileLand - heightScale(0.5);
333 const heightWaterLevel = heightFertileLand - heightScale(3);
334 const heightPassage = heightWaterLevel - heightScale(1.5);
335 const heightIrrigationCanal = heightWaterLevel - heightScale(4);
336 const heightSeaGround = heightWaterLevel - heightScale(8);
337 const heightHill = heightDesert + heightScale(4);
338 const heightHilltop = heightHill + heightScale(90);
339 const heightHillArchers = (heightHilltop + heightHill) / 2;
340 const heightOffsetPath = heightScale(-2.5);
341 const heightOffsetWalls = heightScale(2.5);
343 g_Map.log("Flattening land");
344 createArea(
345         new MapBoundsPlacer(),
346         new ElevationPainter(heightDesert),
347         new HeightConstraint(-Infinity, heightDesert));
349 // Fertile land
350 paintRiver({
351         "parallel": true,
352         "start": new Vector2D(mapBounds.left, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
353         "end": new Vector2D(mapBounds.right, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
354         "width": fractionToTiles(0.65),
355         "fadeDist": 8,
356         "deviation": 0,
357         "heightLand": heightDesert,
358         "heightRiverbed": heightFertileLand,
359         "meanderShort": 40,
360         "meanderLong": 0,
361         "waterFunc": (position, height, riverFraction) => {
362                 createTerrain(tGrass).place(position);
363                 clFertileLand.add(position);
364         },
365         "landFunc": (position, shoreDist1, shoreDist2) => {
367                 for (let riv of layoutFertileLandTextures)
368                         if (riv.left < +shoreDist1 && +shoreDist1 < riv.right ||
369                             riv.left < -shoreDist2 && -shoreDist2 < riv.right)
370                         {
371                                 riv.tileClass.add(position);
372                                 riv.terrain.place(position);
373                         }
374         }
377 // Nile
378 paintRiver({
379         "parallel": true,
380         "start": new Vector2D(mapBounds.left, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
381         "end": new Vector2D(mapBounds.right, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
382         "width": fractionToTiles(0.2),
383         "fadeDist": 4,
384         "deviation": 0,
385         "heightLand": heightFertileLand,
386         "heightRiverbed": heightSeaGround,
387         "meanderShort": 40,
388         "meanderLong": 0
390 Engine.SetProgress(30);
392 g_Map.log("Computing player locations");
393 const playerIDs = primeSortAllPlayers();
394 const playerPosition = playerPlacementCustomAngle(
395         fractionToTiles(0.38),
396         mapCenter,
397         i => Math.PI * (-0.42 / numPlayers * (i + i % 2) - (i % 2) / 2))[0];
399 if (!isNomad())
401         g_Map.log("Marking player positions");
402         for (let position of playerPosition)
403                 addCivicCenterAreaToClass(position, clPlayer);
406 g_Map.log("Marking water");
407 createArea(
408                 new MapBoundsPlacer(),
409                 [
410                         new TileClassPainter(clWater),
411                         new TileClassUnPainter(clFertileLand)
412                 ],
413                 new HeightConstraint(-Infinity, heightWaterLevel));
415 g_Map.log("Marking desert");
416 createArea(
417         new MapBoundsPlacer(),
418         new TileClassPainter(clDesert),
419         [
420                 new HeightConstraint(-Infinity, heightHill),
421                 avoidClasses(clWater, 0, clFertileLand, 0)
422         ]);
424 g_Map.log("Finding possible irrigation canal locations");
425 var irrigationCanalAreas = [];
426 for (let i = 0; i < 30; ++i)
428         let x = fractionToTiles(randFloat(0, 1));
429         irrigationCanalAreas.push(
430                 createArea(
431                         new PathPlacer(
432                                 new Vector2D(x, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
433                                 new Vector2D(x, mapBounds.top).rotateAround(-riverAngle, mapCenter),
434                                 3,
435                                 0,
436                                 10,
437                                 0.1,
438                                 0.01,
439                                 Infinity),
440                         undefined,
441                         avoidClasses(clDesert, 2)));
444 g_Map.log("Creating irrigation canals");
445 var irrigationCanalLocations = [];
446 for (let area of irrigationCanalAreas)
447         {
448                 if (!area.getPoints().length ||
449                     area.getPoints().some(point => !avoidClasses(clPlayer, scaleByMapSize(8, 13), clIrrigationCanal, scaleByMapSize(15, 25)).allows(point)))
450                         continue;
452                 irrigationCanalLocations.push(pickRandom(area.getPoints()).clone().rotateAround(riverAngle, mapCenter).x);
453                 createArea(
454                         new MapBoundsPlacer(),
455                         [
456                                 new SmoothElevationPainter(ELEVATION_SET, heightIrrigationCanal, 1),
457                                 new TileClassPainter(clIrrigationCanal)
458                         ],
459                         [new StayAreasConstraint([area]), new HeightConstraint(heightIrrigationCanal, heightDesert)]);
460         }
462 g_Map.log("Creating passages");
463 irrigationCanalLocations.sort((a, b) => a - b);
464 for (let i = 0; i < irrigationCanalLocations.length; ++i)
466         let previous = i == 0 ? mapBounds.left : irrigationCanalLocations[i - 1];
467         let next = i == irrigationCanalLocations.length - 1 ? mapBounds.right : irrigationCanalLocations[i + 1];
469         let x1 = (irrigationCanalLocations[i] + previous) / 2;
470         let x2 = (irrigationCanalLocations[i] + next) / 2;
471         let y;
473         // The passages should be at different locations, so that enemies can't attack each other easily
474         for (let tries = 0; tries < 100; ++tries)
475         {
476                 y = randIntInclusive(0, mapCenter.y);
477                 let pos = new Vector2D((x1 + x2) / 2, y).rotateAround(-riverAngle, mapCenter).round();
479                 if (g_Map.validTilePassable(new Vector2D(pos.x, pos.y)) &&
480                     avoidClasses(clDesert, 12).allows(pos) &&
481                     new HeightConstraint(heightIrrigationCanal, heightFertileLand).allows(pos))
482                         break;
483         }
485         createPassage({
486                 "start": new Vector2D(x1, y).rotateAround(-riverAngle, mapCenter),
487                 "end": new Vector2D(x2, y).rotateAround(-riverAngle, mapCenter),
488                 "startHeight": heightPassage,
489                 "endHeight": heightPassage,
490                 "constraints": [new HeightConstraint(-Infinity, heightPassage), stayClasses(clFertileLand, 2)],
491                 "tileClass": clPassage,
492                 "startWidth": 10,
493                 "endWidth": 10,
494                 "smoothWidth": 2
495         });
497 Engine.SetProgress(40);
499 g_Map.log("Marking hill");
500 createArea(
501         new MapBoundsPlacer(),
502         new TileClassPainter(clHill),
503         new HeightConstraint(heightHill, Infinity));
505 g_Map.log("Marking water");
506 const areaWater = createArea(
507         new MapBoundsPlacer(),
508         new TileClassPainter(clWater),
509         new HeightConstraint(-Infinity, heightWaterLevel));
511 g_Map.log("Painting water and shoreline");
512 createArea(
513         new MapBoundsPlacer(),
514         new TerrainPainter(tWater),
515         new HeightConstraint(-Infinity, heightShoreline));
517 g_Map.log("Painting hill");
518 const areaHill = createArea(
519         new MapBoundsPlacer(),
520         new TerrainPainter(tHillGround),
521         new HeightConstraint(heightHill, Infinity));
523 g_Map.log("Painting hilltop");
524 const areaHilltop = createArea(
525         new MapBoundsPlacer(),
526         new TerrainPainter(tHilltop),
527         [
528                 new HeightConstraint(heightHilltop, Infinity),
529                 new SlopeConstraint(-Infinity, 2)
530         ]);
532 Engine.SetProgress(50);
534 for (let i = 0; i < numPlayers; ++i)
536         let isDesert = clDesert.has(playerPosition[i]);
537         placePlayerBase({
538                 "playerID": playerIDs[i],
539                 "playerPosition": playerPosition[i],
540                 "PlayerTileClass": clPlayer,
541                 "BaseResourceClass": clBaseResource,
542                 "baseResourceConstraint": avoidClasses(clPlayer, 4, clWater, 4),
543                 "Walls": mapSize <= 256 ? "towers" : "walls",
544                 "CityPatch": {
545                         "outerTerrain": isDesert ? tRoadDesert : tRoadFertileLand,
546                         "innerTerrain": isDesert ? tRoadDesert : tRoadFertileLand
547                 },
548                 "Chicken": {
549                         "template": oGazelle,
550                         "distance": 15,
551                         "minGroupDistance": 2,
552                         "maxGroupDistance": 4,
553                         "minGroupCount": 2,
554                         "maxGroupCount": 3
555                 },
556                 "Berries": {
557                         "template": isDesert ? oBerryBushDesert : oBerryBushGrapes
558                 },
559                 "Mines": {
560                         "types": [
561                                 { "template": oMetalLarge },
562                                 { "template": oStoneLarge }
563                         ]
564                 },
565                 "Trees": {
566                         "template": isDesert ? oAcacia : pickRandom(oPalms),
567                         "count": isDesert ? scaleByMapSize(5, 10) : scaleByMapSize(15, 30)
568                 },
569                 "Treasures": {
570                         "types":
571                         [
572                                 {
573                                         "template": oWoodTreasure,
574                                         "count": isDesert ? 4 : 0
575                                 },
576                                 {
577                                         "template": oStoneTreasure,
578                                         "count": isDesert ? 1 : 0
579                                 },
580                                 {
581                                         "template": oMetalTreasure,
582                                         "count": isDesert ? 1 : 0
583                                 }
584                         ]
585                 },
586                 "Decoratives": {
587                         "template": isDesert ? aRock : pickRandom(aBushesFertileLand)
588                 }
589         });
592 g_Map.log("Placing pyramids");
593 const areaPyramids = createArea(new DiskPlacer(scaleByMapSize(5, 14), positionPyramids));
594 // Retry loops are needed due to the self-avoidance
595 createObjectGroupsByAreas(
596         new SimpleGroup(
597                 [new RandomObject(
598                         ["uncapturable|" + oPyramidLarge, "uncapturable|" + oPyramidSmall],
599                         scaleByMapSize(1, 6),
600                         scaleByMapSize(2, 8),
601                         scaleByMapSize(6, 8),
602                         scaleByMapSize(6, 14),
603                         Math.PI * 1.35,
604                         Math.PI * 1.5,
605                         scaleByMapSize(6, 8))],
606                 true,
607                 clPyramid),
608         0,
609         undefined,
610         1,
611         50,
612         [areaPyramids]);
614 Engine.SetProgress(60);
616 // The city is a circle segment of this maximum size
617 g_Map.log("Computing city grid");
618 var gridCenter = new Vector2D(0, fractionToTiles(0.3)).rotate(-riverAngle).add(mapCenter).round();
619 var gridMaxAngle = scaleByMapSize(Math.PI / 3, Math.PI);
620 var gridStartAngle = -Math.PI / 2 -gridMaxAngle / 2 + riverAngle;
621 var gridRadius = y => hillRadius + 18 * y;
623 var gridPointsX = layoutKushTemples.length;
624 var gridPointsY = Math.floor(scaleByMapSize(2, 4));
625 var gridPointXCenter = Math.floor(gridPointsX / 2);
626 var gridPointYCenter = Math.floor(gridPointsY / 2);
628 // Maps from grid position to map position
629 var cityGridPosition = [];
630 var cityGridAngle = [];
631 for (let y = 0; y < gridPointsY; ++y)
632         [cityGridPosition[y], cityGridAngle[y]] = distributePointsOnCircularSegment(
633                 gridPointsX, gridMaxAngle * (gridPointsX + 1) / gridPointsX, gridStartAngle, gridRadius(y), gridCenter);
635 g_Map.log("Marking city path crossings");
636 for (let y in cityGridPosition)
637         for (let x in cityGridPosition[y])
638         {
639                 cityGridPosition[y][x].round();
640                 createArea(
641                         new DiskPlacer(pathWidth, cityGridPosition[y][x]),
642                         [
643                                 new TileClassPainter(clPath),
644                                 new TileClassPainter(clPathCrossing)
645                         ]);
646         }
648 g_Map.log("Marking horizontal city paths");
649 for (let y = 0; y < gridPointsY; ++y)
650         for (let x = 1; x < gridPointsX; ++x)
651         {
652                 let width = y == gridPointYCenter ? pathWidthSecondary : pathWidth;
653                 createArea(
654                         new PathPlacer(cityGridPosition[y][x - 1], cityGridPosition[y][x], width, 0, 8, 0, 0, Infinity),
655                         new TileClassPainter(clPath));
656         }
658 g_Map.log("Marking vertical city paths");
659 for (let y = 1; y < gridPointsY; ++y)
660         for (let x = 0; x < gridPointsX; ++x)
661         {
662                 let width =
663                         Math.abs(x - gridPointXCenter) == 0 ?
664                                 pathWidthCenter :
665                         Math.abs(x - gridPointXCenter) == 1 ?
666                                 pathWidthSecondary :
667                                 pathWidth;
669                 createArea(
670                         new PathPlacer(cityGridPosition[y - 1][x], cityGridPosition[y][x], width, 0, 8, 0, 0, Infinity),
671                         new TileClassPainter(clPath));
672         }
673 Engine.SetProgress(70);
675 g_Map.log("Placing kushite temples");
676 var entitiesTemples = [];
677 for (let i = 0; i < layoutKushTemples.length; ++i)
679         let x = i + (gridPointsX - layoutKushTemples.length) / 2;
680         let templePosition = Vector2D.add(cityGridPosition[0][x], layoutKushTemples[i].pathOffset.rotate(-Math.PI / 2 - cityGridAngle[0][x]));
681         entitiesTemples.push(g_Map.placeEntityPassable(layoutKushTemples[i].template, 0, templePosition, cityGridAngle[0][x]));
684 g_Map.log("Marking temple area");
685 createArea(
686         new EntitiesObstructionPlacer(entitiesTemples, 0, Infinity),
687         new TileClassPainter(clTemple));
689 g_Map.log("Smoothing temple ground");
690 createArea(
691         new MapBoundsPlacer(),
692         new ElevationBlendingPainter(heightDesert, 0.8),
693         new NearTileClassConstraint(clTemple, 0));
695 g_Map.log("Painting cliffs");
696 createArea(
697         new MapBoundsPlacer(),
698         [
699                 new TerrainPainter(tHillCliff),
700                 new TileClassPainter(clCliff)
701         ],
702         [
703                 stayClasses(clHill, 0),
704                 new SlopeConstraint(2, Infinity)
705         ]);
707 g_Map.log("Painting temple ground");
708 createArea(
709         new MapBoundsPlacer(),
710         new TerrainPainter(tPathWild),
711         [
712                 new NearTileClassConstraint(clTemple, 1),
713                 avoidClasses(clPath, 0, clCliff, 0)
714         ]);
716 g_Map.log("Placing lion statues in the central path");
717 var statueCount = scaleByMapSize(10, 40);
718 var centralPathStart = cityGridPosition[0][gridPointXCenter];
719 var centralPathLength = centralPathStart.distanceTo(cityGridPosition[gridPointsY - 1][gridPointXCenter]);
720 var centralPathAngle = cityGridAngle[0][gridPointXCenter];
721 for (let i = 0; i < 2; ++i)
722         for (let stat = 0; stat < statueCount; ++stat)
723         {
724                 let start = new Vector2D(0, pathWidthCenter * 3/4 * (i - 0.5)).rotate(centralPathAngle).add(centralPathStart);
725                 let position = new Vector2D(centralPathLength, 0).mult(stat / statueCount).rotate(-centralPathAngle).add(start).add(new Vector2D(0.5, 0.5));
727                 if (!avoidClasses(clPathCrossing, 2).allows(position))
728                         continue;
730                 g_Map.placeEntityPassable(pickRandom(aStatues), 0, position, centralPathAngle - Math.PI * (i + 0.5));
731                 clPathStatues.add(position.round());
732         }
734 g_Map.log("Placing guardian infantry in the central path");
735 var centralChampionsCount = scaleByMapSize(2, 40);
736 for (let i = 0; i < 2; ++i)
737         for (let champ = 0; champ < centralChampionsCount; ++champ)
738         {
739                 let start = new Vector2D(0, pathWidthCenter * 1/2 * (i - 0.5)).rotate(-centralPathAngle).add(centralPathStart);
740                 let position = new Vector2D(centralPathLength, 0).mult(champ / centralChampionsCount).rotate(-centralPathAngle).add(start).add(new Vector2D(0.5, 0.5));
742                 if (!avoidClasses(clPathCrossing, 2).allows(position))
743                         continue;
745                 g_Map.placeEntityPassable(pickRandom(oKushChampions), 0, position, centralPathAngle - Math.PI * (i - 0.5));
746                 clPathStatues.add(position.round());
747         }
749 g_Map.log("Placing kushite statues in the secondary paths");
750 for (let x of [gridPointXCenter - 1, gridPointXCenter + 1])
752         g_Map.placeEntityAnywhere(aStatueKush, 0, cityGridPosition[gridPointYCenter][x], cityGridAngle[gridPointYCenter][x]);
753         clPathStatues.add(cityGridPosition[gridPointYCenter][x]);
756 g_Map.log("Painting city paths");
757 var areaPaths = createArea(
758         new MapBoundsPlacer(),
759         [
760                 new LayeredPainter([tPathWild, tPath], [1]),
761                 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetPath, 1)
762         ],
763         stayClasses(clPath, 0));
765 g_Map.log("Placing triggerpoints on city paths");
766 createObjectGroupsByAreas(
767         new SimpleGroup([new SimpleObject(oTriggerPointCityPath, 1, 1, 0, 0)], true, clTriggerPointCityPath),
768         0,
769         [avoidClasses(clTriggerPointCityPath, 8), stayClasses(clPathCrossing, 2)],
770         scaleByMapSize(20, 100),
771         30,
772         [areaPaths]);
774 g_Map.log("Placing city districts");
775 for (let y = 1; y < gridPointsY; ++y)
776         for (let x = 1; x < gridPointsX; ++x)
777         createArea(
778                 new ConvexPolygonPlacer([cityGridPosition[y - 1][x - 1], cityGridPosition[y - 1][x], cityGridPosition[y][x - 1], cityGridPosition[y][x]], Infinity),
779                 [
780                         new TerrainPainter(tRoadDesert),
781                         new CityPainter(layoutKushCity, (-cityGridAngle[y][x - 1] - cityGridAngle[y][x]) / 2, 0),
782                         new TileClassPainter(clCity)
783                 ],
784                 new StaticConstraint(avoidClasses(clPath, 0)));
786 if (placeNapataWall)
788         g_Map.log("Placing front walls");
789         let wallGridMaxAngleSummand = Math.PI / 32;
790         let wallGridStartAngle = gridStartAngle - wallGridMaxAngleSummand / 2;
791         let wallGridRadiusFront = gridRadius(gridPointsY - 1) + pathWidth - 1;
792         let wallGridMaxAngleFront = (gridMaxAngle + wallGridMaxAngleSummand) * gridPointsX / gridPointsX - wallGridMaxAngleSummand / 2;
793         let entitiesWalls = placeCircularWall(
794                 gridCenter,
795                 wallGridRadiusFront,
796                 ["tower", "short", "tower", "gate", "tower", "medium", "tower", "short"],
797                 "napata",
798                 0,
799                 wallGridStartAngle,
800                 wallGridMaxAngleFront,
801                 0,
802                 true,
803                 0);
805         g_Map.log("Placing side and back walls");
806         let wallGridRadiusBack = hillRadius - scaleByMapSize(15, 25);
807         let wallGridMaxAngleBack = (gridMaxAngle + wallGridMaxAngleSummand) * (gridPointsX + 1) / gridPointsX;
808         let wallGridPositionFront = distributePointsOnCircularSegment(gridPointsX, wallGridMaxAngleBack, wallGridStartAngle, wallGridRadiusFront, gridCenter)[0];
809         let wallGridPositionBack = distributePointsOnCircularSegment(gridPointsX, wallGridMaxAngleBack, wallGridStartAngle, wallGridRadiusBack, gridCenter)[0];
810         let wallGridPosition = [wallGridPositionFront[0], ...wallGridPositionBack, wallGridPositionFront[wallGridPositionFront.length - 1]];
811         for (let x = 1; x < wallGridPosition.length; ++x)
812                 entitiesWalls = entitiesWalls.concat(
813                         placeLinearWall(
814                                 wallGridPosition[x - 1],
815                                 wallGridPosition[x],
816                                 ["tower", "gate", "tower", "short", "tower", "short", "tower"],
817                                 "napata",
818                                 0,
819                                 false,
820                                 avoidClasses(clHill, 0, clTemple, 0)));
822         g_Map.log("Marking walls");
823         createArea(
824                 new EntitiesObstructionPlacer(entitiesWalls, 0, Infinity),
825                 new TileClassPainter(clWall));
827         g_Map.log("Marking gates");
828         let entitiesGates = entitiesWalls.filter(entity => entity.templateName.endsWith(oWallGate));
829         createArea(
830                 new EntitiesObstructionPlacer(entitiesGates, 0, Infinity),
831                 new TileClassPainter(clGate));
833         g_Map.log("Painting wall terrain");
834         createArea(
835                 new MapBoundsPlacer(),
836                 [
837                         new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetWalls, 2),
838                         new TerrainPainter(tPathWild)
839                 ],
840                 [
841                         new NearTileClassConstraint(clWall, 1),
842                         avoidClasses(clCliff, 0)
843                 ]);
845 Engine.SetProgress(70);
847 g_Map.log("Marking city palm area");
848 var areaCityPalms =
849         createArea(
850                 new MapBoundsPlacer(),
851                 undefined,
852                 new StaticConstraint([
853                         new NearTileClassConstraint(clPath, 1),
854                         avoidClasses(
855                                 clPath, 0,
856                                 clPyramid, 20,
857                                 clTemple, 3,
858                                 clWall, 3,
859                                 clTower, 1,
860                                 clFortress, 1,
861                                 clPyramid, 1,
862                                 clHouse, 1,
863                                 clBlacksmith, 1,
864                                 clElephantStables, 1,
865                                 clStable, 1,
866                                 clCivicCenter, 1,
867                                 clBarracks, 1,
868                                 clBlemmyeCamp, 1,
869                                 clNubaVillage, 1,
870                                 clMarket, 1)
871                 ]));
873 g_Map.log("Placing city palms");
874 createObjectGroupsByAreas(
875         new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
876         0,
877         avoidClasses(clForest, 2),
878         scaleByMapSize(40, 400),
879         15,
880         [areaCityPalms]);
882 if (placeNapataWall)
884         g_Map.log("Marking wall palm area");
885         var areaWallPalms = createArea(
886                 new MapBoundsPlacer(),
887                 undefined,
888                 new StaticConstraint([
889                         new NearTileClassConstraint(clWall, 2),
890                         avoidClasses(clPath, 1, clWall, 1, clGate, 3, clTemple, 2, clHill, 6)
891                 ]));
893         g_Map.log("Placing city palms");
894         createObjectGroupsByAreas(
895                 new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
896                 0,
897                 avoidClasses(clForest, 2),
898                 scaleByMapSize(40, 200),
899                 50,
900                 [areaWallPalms]);
903 createBumps(new StaticConstraint(avoidClasses(clPlayer, 6, clCity, 0, clWater, 2, clHill, 0, clPath, 0, clTemple, 4, clPyramid, 8)), scaleByMapSize(30, 300), 1, 8, 4, 0, 3);
904 Engine.SetProgress(75);
906 g_Map.log("Setting up common constraints");
907 const stayDesert = new StaticConstraint(stayClasses(clDesert, 0));
908 const stayFertileLand = new StaticConstraint(stayClasses(clFertileLand, 0));
909 const nearWater = new NearTileClassConstraint(clWater, 3);
910 var avoidCollisions = new AndConstraint(
911         [
912                 new StaticConstraint(avoidClasses(clCliff, 0, clHill, 0, clPlayer, 15, clWater, 1, clPath, 2, clTemple, 4, clPyramid, 7, clCity, 4, clWall, 4, clGate, 8)),
913                 avoidClasses(clForest, 1, clRock, 4, clMetal, 4, clFood, 6, clSoldier, 1, clTreasure, 1)
914         ]);
916 g_Map.log("Setting up common areas");
917 const areaDesert = createArea(new MapBoundsPlacer(), undefined, stayDesert);
918 const areaFertileLand = createArea(new MapBoundsPlacer(), undefined, stayFertileLand);
920 createForests(
921         [tForestFloorFertile, tForestFloorFertile, tForestFloorFertile, pForestPalms, pForestPalms],
922         [stayFertileLand, avoidClasses(clForest, 15), new StaticConstraint([avoidClasses(clWater, 2), avoidCollisions])],
923         clForest,
924         scaleByMapSize(250, 2000));
926 const avoidCollisionsMines = new StaticConstraint([
927         isNomad() ? new NullConstraint() : avoidClasses(clFertileLand, 10),
928         avoidClasses(
929                 clWater, 4, clCliff, 4, clCity, 4,
930                 clPlayer, 20, clForest, 4, clPyramid, 6, clTemple, 4, clPath, 4, clGate, 8)]);
932 g_Map.log("Creating stone mines");
933 createMines(
934         [
935                 [new SimpleObject(oStoneSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oStoneLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)],
936                 [new SimpleObject(oStoneSmall, 2, 5, 1, 3, 0, 2 * Math.PI, 1)]
937         ],
938         [avoidCollisionsMines, avoidClasses(clRock, 10)],
939         clRock,
940         scaleByMapSize(8, 26));
942 g_Map.log("Creating metal mines");
943 createMines(
944         [
945                 [new SimpleObject(oMetalSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oMetalLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)],
946                 [new SimpleObject(oMetalSmall, 2, 5, 1, 3, 0, 2 * Math.PI, 1)]
947         ],
948         [avoidCollisionsMines, avoidClasses(clMetal, 10, clRock, 5)],
949         clMetal,
950         scaleByMapSize(8, 26));
952 g_Map.log("Placing triggerpoints for attackers");
953 createObjectGroups(
954         new SimpleGroup([new SimpleObject(oTriggerPointAttackerPatrol, 1, 1, 0, 0)], true, clTriggerPointMap),
955         0,
956         [avoidClasses(clCity, 8, clCliff, 4, clHill, 4, clWater, 0, clWall, 2, clForest, 1, clRock, 4, clMetal, 4, clTriggerPointMap, 15)],
957         scaleByMapSize(20, 100),
958         30);
960 g_Map.log("Creating berries");
961 createObjectGroupsByAreas(
962         new SimpleGroup([new SimpleObject(oBerryBushGrapes, 4, 6, 1, 2)], true, clFood),
963         0,
964         avoidCollisions,
965         scaleByMapSize(3, 15),
966         50,
967         [areaFertileLand]);
969 g_Map.log("Creating rhinos");
970 createObjectGroupsByAreas(
971         new SimpleGroup([new SimpleObject(oRhino, 1, 1, 0, 1)], true, clFood),
972         0,
973         avoidCollisions,
974         scaleByMapSize(2, 10),
975         50,
976         [areaDesert]);
978 g_Map.log("Creating warthogs");
979 createObjectGroupsByAreas(
980         new SimpleGroup([new SimpleObject(oWarthog, 1, 1, 0, 1)], true, clFood),
981         0,
982         avoidCollisions,
983         scaleByMapSize(2, 10),
984         50,
985         [areaFertileLand]);
987 g_Map.log("Creating giraffes");
988 createObjectGroups(
989         new SimpleGroup([new SimpleObject(oGiraffe, 2, 3, 2, 4), new SimpleObject(oGiraffeInfant, 2, 3, 2, 4)], true, clFood),
990         0,
991         avoidCollisions,
992         scaleByMapSize(2, 10),
993         50);
995 g_Map.log("Creating gazelles");
996 createObjectGroups(
997         new SimpleGroup([new SimpleObject(oGazelle, 5, 7, 2, 4)], true, clFood),
998         0,
999         avoidCollisions,
1000         scaleByMapSize(2, 10),
1001         50,
1002         [areaDesert]);
1004 if (!isNomad())
1006         g_Map.log("Creating lions");
1007         createObjectGroupsByAreas(
1008                 new SimpleGroup([new SimpleObject(oLion, 1, 2, 2, 4), new SimpleObject(oLioness, 2, 3, 2, 4)], true, clFood),
1009                 0,
1010                 [avoidCollisions, avoidClasses(clPlayer, 20)],
1011                 scaleByMapSize(2, 10),
1012                 50,
1013                 [areaDesert]);
1016 g_Map.log("Creating elephants");
1017 createObjectGroupsByAreas(
1018         new SimpleGroup([new SimpleObject(oElephant, 2, 3, 2, 4), new SimpleObject(oElephantInfant, 2, 3, 2, 4)], true, clFood),
1019         0,
1020         avoidCollisions,
1021         scaleByMapSize(2, 10),
1022         50,
1023         [areaDesert]);
1025 g_Map.log("Creating crocodiles");
1026 if (!isNomad())
1027         createObjectGroupsByAreas(
1028                 new SimpleGroup([new SimpleObject(oCrocodile, 2, 3, 3, 5)], true, clFood),
1029                 0,
1030                 [nearWater, avoidCollisions],
1031                 scaleByMapSize(1, 6),
1032                 50,
1033                 [areaFertileLand]);
1035 Engine.SetProgress(85);
1037 g_Map.log("Marking irrigation canal tree area");
1038 var areaIrrigationCanalTrees = createArea(
1039         new MapBoundsPlacer(),
1040         undefined,
1041         [
1042                 nearWater,
1043                 avoidClasses(clPassage, 3),
1044                 avoidCollisions
1045         ]);
1047 g_Map.log("Creating irrigation canal trees");
1048 createObjectGroupsByAreas(
1049         new SimpleGroup([new RandomObject(oPalms, 1, 1, 1, 1)], true, clForest),
1050         0,
1051         avoidClasses(clForest, 1),
1052         scaleByMapSize(100, 600),
1053         50,
1054         [areaIrrigationCanalTrees]);
1056 createStragglerTrees(
1057         oPalms,
1058         [stayFertileLand, avoidCollisions],
1059         clForest,
1060         scaleByMapSize(50, 400),
1061         200);
1063 createStragglerTrees(
1064         [oAcacia],
1065         [stayDesert, avoidCollisions],
1066         clForest,
1067         scaleByMapSize(50, 400),
1068         200);
1070 g_Map.log("Placing archer groups on the hilltop");
1071 createObjectGroupsByAreas(
1072         new SimpleGroup([new RandomObject([oKushCitizenArcher, oKushChampionArcher], scaleByMapSize(4, 10), scaleByMapSize(6, 20), 1, 4)], true, clSoldier),
1073         0,
1074         new StaticConstraint([avoidClasses(clCliff, 1), new NearTileClassConstraint(clCliff, 5)]),
1075         scaleByMapSize(1, 5),
1076         250,
1077         [areaHilltop]);
1079 g_Map.log("Placing individual archers on the hill");
1080 createObjectGroupsByAreas(
1081         new SimpleGroup([new RandomObject([oKushCitizenArcher, oKushChampionArcher], 1, 1, 1, 3)], true, clSoldier),
1082         0,
1083         new StaticConstraint([
1084                 new HeightConstraint(heightHillArchers, heightHilltop),
1085                 avoidClasses(clCliff, 1, clSoldier, 1),
1086                 new NearTileClassConstraint(clCliff, 5)
1087         ]),
1088         scaleByMapSize(8, 100),
1089         250,
1090         [areaHill]);
1092 g_Map.log("Placing siege engines on the hilltop");
1093 createObjectGroupsByAreas(
1094         new SimpleGroup([new RandomObject(oPtolSiege, 1, 1, 1, 3)], true, clSoldier),
1095         0,
1096         new StaticConstraint([new NearTileClassConstraint(clCliff, 5), avoidClasses(clCliff, 1, clSoldier, 1)]),
1097         scaleByMapSize(1, 6),
1098         250,
1099         [areaHilltop]);
1101 g_Map.log("Placing soldiers near pyramids");
1102 const avoidCollisionsPyramids = new StaticConstraint([avoidCollisions, new NearTileClassConstraint(clPyramid, 10)]);
1103 createObjectGroupsByAreas(
1104         new SimpleGroup([new SimpleObject(oKushCitizenArcher, 1, 1, 1, 1)], true, clSoldier),
1105         0,
1106         avoidCollisionsPyramids,
1107         scaleByMapSize(3, 8),
1108         250,
1109         [areaPyramids]);
1111 g_Map.log("Placing treasures at the pyramid");
1112 createObjectGroupsByAreas(
1113         new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
1114         0,
1115         avoidCollisionsPyramids,
1116         scaleByMapSize(1, 10),
1117         250,
1118         [areaPyramids]);
1120 g_Map.log("Placing treasures on the hilltop");
1121 createObjectGroupsByAreas(
1122         new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
1123         0,
1124         new StaticConstraint([avoidClasses(clCliff, 1, clTreasure, 1)]),
1125         scaleByMapSize(8, 35),
1126         250,
1127         [areaHilltop]);
1129 g_Map.log("Placing treasures in the city");
1130 var pathBorderConstraint = new AndConstraint([
1131         new StaticConstraint([new NearTileClassConstraint(clCity, 1)]),
1132         avoidClasses(clTreasure, 2, clStatue, 10, clPathStatues, 4, clWall, 2, clForest, 1)
1134 createObjectGroupsByAreas(
1135         new SimpleGroup([new RandomObject(oTreasuresCity, 1, 1, 0, 2)], true, clTreasure),
1136         0,
1137         pathBorderConstraint,
1138         scaleByMapSize(2, 60),
1139         500,
1140         [areaPaths]);
1142 g_Map.log("Placing handcarts on the paths");
1143 createObjectGroupsByAreas(
1144         new SimpleGroup([new SimpleObject(aHandcart, 1, 1, 1, 1)], true, clDecorative),
1145         0,
1146         [pathBorderConstraint, avoidClasses(clDecorative, 10)],
1147         scaleByMapSize(0, 5),
1148         250,
1149         [areaPaths]);
1151 g_Map.log("Placing fence in fertile land");
1152 createObjectGroupsByAreas(
1153         new SimpleGroup([new SimpleObject(aPlotFence, 1, 1, 1, 1)], true, clDecorative),
1154         0,
1155         new StaticConstraint(avoidCollisions, avoidClasses(clWater, 6, clDecorative, 10)),
1156         scaleByMapSize(1, 10),
1157         250,
1158         [areaFertileLand]);
1160 g_Map.log("Creating fish");
1161 createObjectGroups(
1162         new SimpleGroup([new SimpleObject(oFish, 3, 4, 2, 3)], true, clFood),
1163         0,
1164         [new StaticConstraint(stayClasses(clWater, 6)), avoidClasses(clFood, 12)],
1165         scaleByMapSize(20, 120),
1166         50);
1168 Engine.SetProgress(95);
1170 avoidCollisions = new StaticConstraint(avoidCollisions);
1172 createDecoration(
1173         aBushesDesert.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
1174         aBushesDesert.map(bush => scaleByMapSize(20, 150) * randIntInclusive(1, 3)),
1175         [stayDesert, avoidCollisions]);
1177 createDecoration(
1178         aBushesFertileLand.map(bush => [new SimpleObject(bush, 0, 4, 2, 4)]),
1179         aBushesFertileLand.map(bush => scaleByMapSize(20, 150) * randIntInclusive(1, 3)),
1180         [stayFertileLand, avoidCollisions]);
1182 createDecoration(
1183         [[new SimpleObject(aRock, 0, 4, 2, 4)]],
1184         [[scaleByMapSize(80, 500)]],
1185         [stayDesert, avoidCollisions]);
1187 createDecoration(
1188         aBushesFertileLand.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
1189         aBushesFertileLand.map(bush => scaleByMapSize(100, 800)),
1190         [new HeightConstraint(heightWaterLevel, heightShoreline), avoidCollisions]);
1192 g_Map.log("Creating reeds");
1193 createObjectGroupsByAreas(
1194         new SimpleGroup([new RandomObject(aWaterDecoratives, 2, 4, 1, 2)], true),
1195         0,
1196         new StaticConstraint(new NearTileClassConstraint(clFertileLand, 4)),
1197         scaleByMapSize(50, 400),
1198         20,
1199         [areaWater]);
1201 g_Map.log("Creating hawk");
1202 for (let i = 0; i < scaleByMapSize(0, 2); ++i)
1203         g_Map.placeEntityAnywhere(oHawk, 0, mapCenter, randomAngle());
1205 placePlayersNomad(clPlayer, [stayClasses(clFertileLand, 0), avoidClasses(clCity, 15), avoidCollisions]);
1207 setWindAngle(-0.43);
1208 setWaterHeight(heightWaterLevel + SEA_LEVEL);
1209 setWaterTint(0.161, 0.286, 0.353);
1210 setWaterColor(0.129, 0.176, 0.259);
1211 setWaterWaviness(8);
1212 setWaterMurkiness(0.87);
1213 setWaterType("lake");
1215 setTerrainAmbientColor(0.58, 0.443, 0.353);
1217 setSunColor(0.733, 0.746, 0.574);
1218 setSunRotation(Math.PI / 2 * randFloat(-1, 1));
1219 setSunElevation(Math.PI / 7);
1221 setFogFactor(0);
1222 setFogThickness(0);
1223 setFogColor(0.69, 0.616, 0.541);
1225 setPPEffect("hdr");
1226 setPPContrast(0.67);
1227 setPPSaturation(0.42);
1228 setPPBloom(0.23);
1230 g_Map.ExportMap();