Fix player placement and hero rush on Jebel Barkal.
[0ad.git] / binaries / data / mods / public / maps / random / jebel_barkal.js
blobde6631b3a3675d4ede5831ecbbccd8d8d23f5a13
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 oElephantStables = "structures/kush_elephant_stables";
76 const oKushCitizenArcher = "units/kush_infantry_archer_e";
77 const oKushChampionArcher = "units/kush_champion_infantry";
78 const oPtolSiege = ["units/ptol_mechanical_siege_lithobolos_unpacked", "units/ptol_mechanical_siege_polybolos_unpacked"];
79 const oTriggerPointPath = "trigger/trigger_point_A";
81 const aRock = actorTemplate("geology/stone_savanna_med");
82 const aHandcart = actorTemplate("props/special/eyecandy/handcart_1");
83 const aPlotFence = actorTemplate("props/special/common/plot_fence");
84 const aStatueKush = actorTemplate("props/special/eyecandy/statues_kush");
85 const aStatues = [
86         "props/structures/kushites/statue_lion",
87         "props/structures/kushites/statue_ram"
88 ].map(actorTemplate);
89 const aBushesFertileLand = [
90         ...new Array(3).fill("props/flora/shrub_spikes"),
91         "props/flora/shrub_tropic_plant_a",
92         "props/flora/shrub_tropic_plant_b",
93         "props/flora/shrub_tropic_plant_flower",
94         "props/flora/ferns",
95         "props/flora/foliagebush",
96         "props/flora/bush",
97         "props/flora/bush_medit_la",
98         "props/flora/bush_medit_la_lush",
99         "props/flora/bush_medit_me_lush",
100         "props/flora/bush_medit_sm",
101         "props/flora/bush_medit_sm_lush",
102         "props/flora/bush_tempe_la_lush"
103 ].map(actorTemplate);
104 const aBushesDesert = [
105         "props/flora/bush_dry_a",
106         "props/flora/bush_medit_la_dry",
107         "props/flora/bush_medit_me_dry",
108         "props/flora/bush_medit_sm",
109         "props/flora/bush_medit_sm_dry",
110         "props/flora/bush_tempe_me_dry",
111         "props/flora/grass_soft_dry_large_tall",
112         "props/flora/grass_soft_dry_small_tall"
113 ].map(actorTemplate);
114 const aWaterDecoratives = ["props/flora/reeds_pond_lush_a"].map(actorTemplate);
116 const pForestPalms = [
117         tForestFloorFertile,
118         ...oPalms.map(tree => tForestFloorFertile + TERRAIN_SEPARATOR + tree),
119         tForestFloorFertile];
121 const heightScale = num => num * g_MapSettings.Size / 320;
123 const minHeightSource = 3;
124 const maxHeightSource = 800;
126 const g_Map = new RandomMap(0, tSand);
127 const mapSize = g_Map.getSize();
128 const mapCenter = g_Map.getCenter();
129 const mapBounds = g_Map.getBounds();
130 const numPlayers = getNumPlayers();
132 const clHill = g_Map.createTileClass();
133 const clCliff = g_Map.createTileClass();
134 const clDesert = g_Map.createTileClass();
135 const clFertileLand = g_Map.createTileClass();
136 const clWater = g_Map.createTileClass();
137 const clIrrigationCanal = g_Map.createTileClass();
138 const clPassage = g_Map.createTileClass();
139 const clPlayer = g_Map.createTileClass();
140 const clBaseResource = g_Map.createTileClass();
141 const clFood = g_Map.createTileClass();
142 const clForest = g_Map.createTileClass();
143 const clRock = g_Map.createTileClass();
144 const clMetal = g_Map.createTileClass();
145 const clTreasure = g_Map.createTileClass();
146 const clCity = g_Map.createTileClass();
147 const clPath = g_Map.createTileClass();
148 const clPathStatues = g_Map.createTileClass();
149 const clPathCrossing = g_Map.createTileClass();
150 const clStatue = g_Map.createTileClass();
151 const clTriggerPointPath = g_Map.createTileClass();
152 const clSoldier = g_Map.createTileClass();
154 const clTower = g_Map.createTileClass();
155 const clFortress = g_Map.createTileClass();
156 const clTemple = g_Map.createTileClass();
157 const clPyramid = g_Map.createTileClass();
158 const clBlacksmith = g_Map.createTileClass();
159 const clElephantStables = g_Map.createTileClass();
160 const clCivicCenter = g_Map.createTileClass();
161 const clBarracks = g_Map.createTileClass();
162 const clBlemmyeCamp = g_Map.createTileClass();
163 const clNubaVillage = g_Map.createTileClass();
164 const clMarket = g_Map.createTileClass();
166 const riverAngle = Math.PI * 0.05;
168 const hillRadius = scaleByMapSize(40, 140);
169 const positionPyramids = new Vector2D(fractionToTiles(0.15), fractionToTiles(0.75));
171 const pathWidth = 4;
172 const pathWidthCenter = 10;
173 const pathWidthSecondary = 6;
175 const layoutFertileLandTextures = [
176         {
177                 "left": fractionToTiles(0),
178                 "right": fractionToTiles(0.04),
179                 "terrain": createTerrain(tGrassTransition1),
180                 "tileClass": clFertileLand
181         },
182         {
183                 "left": fractionToTiles(0.04),
184                 "right": fractionToTiles(0.08),
185                 "terrain": createTerrain(tGrassTransition2),
186                 "tileClass": clDesert
187         }
190 var layoutKushTemples = [
191         ...new Array(2).fill(0).map((v, i) =>
192         ({
193                 "template": oTempleApedemak,
194                 "pathOffset": new Vector2D(0, 9),
195                 "minMapSize": i == 0 ? 320 : 0
196         })),
197         {
198                 "template": oTempleAmun,
199                 "pathOffset": new Vector2D(0, 12),
200                 "minMapSize": 256
201         },
202         {
203                 "template": oWonderPtol,
204                 "pathOffset": new Vector2D(0,  scaleByMapSize(9, 14)),
205                 "minMapSize": 0
206         },
207         {
208                 "template": oTempleAmun,
209                 "pathOffset": new Vector2D(0, 12),
210                 "minMapSize": 256
211         },
212         ...new Array(2).fill(0).map((v, i) =>
213         ({
214                 "template": oTempleApedemak,
215                 "pathOffset": new Vector2D(0, 9),
216                 "minMapSize": i == 0 ? 0 : 320
217         }))
218 ].filter(temple => mapSize >= temple.minMapSize);
221  * The buildings are set as uncapturable, otherwise the player would gain the buildings via root territory and can delete them without effort.
222  * Keep the entire city uncapturable as a consistent property of the city.
223  */
224 const layoutKushCity = [
225         {
226                 "templateName": "uncapturable|" + oHouse
227         },
228         {
229                 "templateName": "uncapturable|" + oFortress,
230                 "constraints": [avoidClasses(clFortress, 25), new NearTileClassConstraint(clPath, 8)],
231                 "painters": new TileClassPainter(clFortress)
232         },
233         {
234                 "templateName": "uncapturable|" + oCivicCenter,
235                 "constraints": [avoidClasses(clCivicCenter, 60), new NearTileClassConstraint(clPath, 8)],
236                 "painters": new TileClassPainter(clCivicCenter)
237         },
238         {
239                 "templateName": "uncapturable|" + oElephantStables,
240                 "constraints": avoidClasses(clElephantStables, 10),
241                 "painters": new TileClassPainter(clElephantStables)
242         },
243         {
244                 "templateName": "uncapturable|" + oBarracks,
245                 "constraints": avoidClasses(clBarracks, 12),
246                 "painters": new TileClassPainter(clBarracks)
247         },
248         {
249                 "templateName": "uncapturable|" + oTower,
250                 "constraints": avoidClasses(clTower, 17),
251                 "painters": new TileClassPainter(clTower)
252         },
253         {
254                 "templateName": "uncapturable|" + oMarket,
255                 "constraints": avoidClasses(clMarket, 15),
256                 "painters": new TileClassPainter(clMarket)
257         },
258         {
259                 "templateName": "uncapturable|" + oBlacksmith,
260                 "constraints": avoidClasses(clBlacksmith, 30),
261                 "painters": new TileClassPainter(clBlacksmith)
262         },
263         {
264                 "templateName": "uncapturable|" + oNubaVillage,
265                 "constraints": avoidClasses(clNubaVillage, 30),
266                 "painters": new TileClassPainter(clNubaVillage)
267         },
268         {
269                 "templateName": "uncapturable|" + oBlemmyeCamp,
270                 "constraints": avoidClasses(clBlemmyeCamp, 30),
271                 "painters": new TileClassPainter(clBlemmyeCamp)
272         }
274 Engine.SetProgress(10);
276 g_Map.log("Loading hill heightmap");
277 createArea(
278         new MapBoundsPlacer(),
279         new HeightmapPainter(
280                 translateHeightmap(
281                         new Vector2D(-12, scaleByMapSize(-12, -25)),
282                         undefined,
283                         convertHeightmap1Dto2D(Engine.LoadMapTerrain("maps/random/jebel_barkal.pmp").height)),
284                 minHeightSource,
285                 maxHeightSource));
287 const heightDesert = g_Map.getHeight(mapCenter);
288 const heightFertileLand = heightDesert - heightScale(2);
289 const heightShoreline = heightFertileLand - heightScale(0.5);
290 const heightWaterLevel = heightFertileLand - heightScale(3);
291 const heightPassage = heightWaterLevel - heightScale(1.5);
292 const heightIrrigationCanal = heightWaterLevel - heightScale(4);
293 const heightSeaGround = heightWaterLevel - heightScale(8);
294 const heightHill = heightDesert + heightScale(4);
295 const heightHilltop = heightHill + heightScale(90);
296 const heightHillArchers = (heightHilltop + heightHill) / 2;
297 const heightOffsetPath = heightScale(-2.5);
299 g_Map.log("Flattening land");
300 createArea(
301         new MapBoundsPlacer(),
302         new ElevationPainter(heightDesert),
303         new HeightConstraint(-Infinity, heightDesert));
305 // Fertile land
306 paintRiver({
307         "parallel": true,
308         "start": new Vector2D(mapBounds.left, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
309         "end": new Vector2D(mapBounds.right, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
310         "width": fractionToTiles(0.65),
311         "fadeDist": 8,
312         "deviation": 0,
313         "heightLand": heightDesert,
314         "heightRiverbed": heightFertileLand,
315         "meanderShort": 40,
316         "meanderLong": 0,
317         "waterFunc": (position, height, riverFraction) => {
318                 createTerrain(tGrass).place(position);
319                 clFertileLand.add(position);
320         },
321         "landFunc": (position, shoreDist1, shoreDist2) => {
323                 for (let riv of layoutFertileLandTextures)
324                         if (riv.left < +shoreDist1 && +shoreDist1 < riv.right ||
325                             riv.left < -shoreDist2 && -shoreDist2 < riv.right)
326                         {
327                                 riv.tileClass.add(position);
328                                 riv.terrain.place(position);
329                         }
330         }
333 // Nile
334 paintRiver({
335         "parallel": true,
336         "start": new Vector2D(mapBounds.left, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
337         "end": new Vector2D(mapBounds.right, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
338         "width": fractionToTiles(0.2),
339         "fadeDist": 4,
340         "deviation": 0,
341         "heightLand": heightFertileLand,
342         "heightRiverbed": heightSeaGround,
343         "meanderShort": 40,
344         "meanderLong": 0
346 Engine.SetProgress(30);
348 g_Map.log("Computing player locations");
349 const playerIDs = primeSortAllPlayers();
350 const playerPosition = playerPlacementCustomAngle(
351         fractionToTiles(0.38),
352         mapCenter,
353         i => Math.PI * (-0.42 / numPlayers * (i + i % 2) - (i % 2) / 2))[0];
355 g_Map.log("Marking player positions");
356 for (let position of playerPosition)
357         addCivicCenterAreaToClass(position, clPlayer);
359 g_Map.log("Marking water");
360 createArea(
361                 new MapBoundsPlacer(),
362                 [
363                         new TileClassPainter(clWater),
364                         new TileClassUnPainter(clFertileLand)
365                 ],
366                 new HeightConstraint(-Infinity, heightWaterLevel));
368 g_Map.log("Marking desert");
369 createArea(
370         new MapBoundsPlacer(),
371         new TileClassPainter(clDesert),
372         [
373                 new HeightConstraint(-Infinity, heightHill),
374                 avoidClasses(clWater, 0, clFertileLand, 0)
375         ]);
377 g_Map.log("Finding possible irrigation canal locations");
378 var irrigationCanalAreas = [];
379 for (let i = 0; i < 30; ++i)
381         let x = fractionToTiles(randFloat(0, 1));
382         irrigationCanalAreas.push(
383                 createArea(
384                         new PathPlacer(
385                                 new Vector2D(x, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
386                                 new Vector2D(x, mapBounds.top).rotateAround(-riverAngle, mapCenter),
387                                 3,
388                                 0,
389                                 10,
390                                 0.1,
391                                 0.01,
392                                 Infinity),
393                         undefined,
394                         avoidClasses(clDesert, 2)));
397 g_Map.log("Creating irrigation canals");
398 var irrigationCanalLocations = [];
399 for (let area of irrigationCanalAreas)
400         {
401                 if (!area.getPoints().length ||
402                     area.getPoints().some(point => !avoidClasses(clPlayer, scaleByMapSize(8, 13), clIrrigationCanal, scaleByMapSize(15, 25)).allows(point)))
403                         continue;
405                 irrigationCanalLocations.push(pickRandom(area.getPoints()).clone().rotateAround(riverAngle, mapCenter).x);
406                 createArea(
407                         new MapBoundsPlacer(),
408                         [
409                                 new SmoothElevationPainter(ELEVATION_SET, heightIrrigationCanal, 1),
410                                 new TileClassPainter(clIrrigationCanal)
411                         ],
412                         [new StayAreasConstraint([area]), new HeightConstraint(heightIrrigationCanal, heightDesert)]);
413         }
415 g_Map.log("Creating passages");
416 irrigationCanalLocations.sort((a, b) => a - b);
417 for (let i = 0; i < irrigationCanalLocations.length; ++i)
419         let previous = i == 0 ? mapBounds.left : irrigationCanalLocations[i - 1];
420         let next = i == irrigationCanalLocations.length - 1 ? mapBounds.right : irrigationCanalLocations[i + 1];
422         let x1 = (irrigationCanalLocations[i] + previous) / 2;
423         let x2 = (irrigationCanalLocations[i] + next) / 2;
424         let y;
426         // The passages should be at different locations, so that enemies can't attack each other easily
427         for (let tries = 0; tries < 100; ++tries)
428         {
429                 y = randIntInclusive(0, mapCenter.y);
430                 let pos = new Vector2D((x1 + x2) / 2, y).rotateAround(-riverAngle, mapCenter).round();
432                 if (g_Map.validTilePassable(new Vector2D(pos.x, pos.y)) &&
433                     avoidClasses(clDesert, 12).allows(pos) &&
434                     new HeightConstraint(heightIrrigationCanal, heightFertileLand).allows(pos))
435                         break;
436         }
438         createPassage({
439                 "start": new Vector2D(x1, y).rotateAround(-riverAngle, mapCenter),
440                 "end": new Vector2D(x2, y).rotateAround(-riverAngle, mapCenter),
441                 "startHeight": heightPassage,
442                 "endHeight": heightPassage,
443                 "constraints": [new HeightConstraint(-Infinity, heightPassage), stayClasses(clFertileLand, 2)],
444                 "tileClass": clPassage,
445                 "startWidth": 10,
446                 "endWidth": 10,
447                 "smoothWidth": 2
448         });
450 Engine.SetProgress(40);
452 g_Map.log("Marking hill");
453 createArea(
454         new MapBoundsPlacer(),
455         new TileClassPainter(clHill),
456         new HeightConstraint(heightHill, Infinity));
458 g_Map.log("Marking water");
459 const areaWater = createArea(
460         new MapBoundsPlacer(),
461         new TileClassPainter(clWater),
462         new HeightConstraint(-Infinity, heightWaterLevel));
464 g_Map.log("Painting water and shoreline");
465 createArea(
466         new MapBoundsPlacer(),
467         new TerrainPainter(tWater),
468         new HeightConstraint(-Infinity, heightShoreline));
470 g_Map.log("Painting hill");
471 const areaHill = createArea(
472         new MapBoundsPlacer(),
473         new TerrainPainter(tHillGround),
474         new HeightConstraint(heightHill, Infinity));
476 g_Map.log("Painting hilltop");
477 const areaHilltop = createArea(
478         new MapBoundsPlacer(),
479         new TerrainPainter(tHilltop),
480         [
481                 new HeightConstraint(heightHilltop, Infinity),
482                 new SlopeConstraint(-Infinity, 2)
483         ]);
485 g_Map.log("Painting cliffs");
486 createArea(
487         new MapBoundsPlacer(),
488         [
489                 new TerrainPainter(tHillCliff),
490                 new TileClassPainter(clCliff)
491         ],
492         [
493                 stayClasses(clHill, 0),
494                 new SlopeConstraint(2, Infinity)
495         ]);
496 Engine.SetProgress(50);
498 for (let i = 0; i < playerIDs.length; ++i)
500         let isDesert = clDesert.has(playerPosition[i]);
501         placePlayerBase({
502                 "playerID": playerIDs[i],
503                 "playerPosition": playerPosition[i],
504                 "PlayerTileClass": clPlayer,
505                 "BaseResourceClass": clBaseResource,
506                 "baseResourceConstraint": avoidClasses(clPlayer, 4, clWater, 4),
507                 "Walls": mapSize <= 256 ? "towers" : "walls",
508                 "CityPatch": {
509                         "outerTerrain": isDesert ? tRoadDesert : tRoadFertileLand,
510                         "innerTerrain": isDesert ? tRoadDesert : tRoadFertileLand
511                 },
512                 "Chicken": {
513                         "template": oGazelle,
514                         "distance": 15,
515                         "minGroupDistance": 2,
516                         "maxGroupDistance": 4,
517                         "minGroupCount": 2,
518                         "maxGroupCount": 3
519                 },
520                 "Berries": {
521                         "template": isDesert ? oBerryBushDesert : oBerryBushGrapes
522                 },
523                 "Mines": {
524                         "types": [
525                                 { "template": oMetalLarge },
526                                 { "template": oStoneLarge }
527                         ]
528                 },
529                 "Trees": {
530                         "template": isDesert ? oAcacia : pickRandom(oPalms),
531                         "count": isDesert ? scaleByMapSize(5, 10) : scaleByMapSize(15, 30)
532                 },
533                 "Treasures": {
534                         "types":
535                         [
536                                 {
537                                         "template": oWoodTreasure,
538                                         "count": isDesert ? 3 : 0
539                                 },
540                                 {
541                                         "template": oStoneTreasure,
542                                         "count": isDesert ? 1 : 0
543                                 },
544                                 {
545                                         "template": oMetalTreasure,
546                                         "count": isDesert ? 1 : 0
547                                 }
548                         ]
549                 },
550                 "Decoratives": {
551                         "template": isDesert ? aRock : pickRandom(aBushesFertileLand)
552                 }
553         });
556 g_Map.log("Placing pyramids");
557 const areaPyramids = createArea(new DiskPlacer(scaleByMapSize(5, 14), positionPyramids));
558 // Retry loops are needed due to the self-avoidance
559 createObjectGroupsByAreas(
560         new SimpleGroup(
561                 [new RandomObject(
562                         ["uncapturable|" + oPyramidLarge, "uncapturable|" + oPyramidSmall],
563                         scaleByMapSize(1, 6),
564                         scaleByMapSize(2, 8),
565                         scaleByMapSize(6, 8),
566                         scaleByMapSize(6, 14),
567                         Math.PI * 1.35,
568                         Math.PI * 1.5,
569                         scaleByMapSize(6, 8))],
570                 true,
571                 clPyramid),
572         0,
573         undefined,
574         1,
575         50,
576         [areaPyramids]);
578 Engine.SetProgress(60);
580 // The city is a circle segment of this maximum size
581 g_Map.log("Computing city grid");
582 var gridCenter = new Vector2D(0, fractionToTiles(0.3)).rotate(-riverAngle).add(mapCenter).round();
583 var gridMaxAngle = scaleByMapSize(Math.PI / 4, Math.PI);
584 var gridStartAngle = -Math.PI / 2 -gridMaxAngle / 2 + riverAngle;
585 var gridRadius = y => hillRadius + scaleByMapSize(10, 25) * y;
586 var gridPointsX = layoutKushTemples.length;
587 var gridPointsY = Math.floor(scaleByMapSize(2, 4));
588 var gridPointXCenter = Math.floor(gridPointsX / 2);
589 var gridPointYCenter = Math.floor(gridPointsY / 2);
591 // Maps from grid position to map position
592 var cityGridPosition = [];
593 var cityGridAngle = [];
594 for (let y = 0; y < gridPointsY; ++y)
595         [cityGridPosition[y], cityGridAngle[y]] = distributePointsOnCircularSegment(gridPointsX, gridMaxAngle * (gridPointsX + 1) / gridPointsX, gridStartAngle, gridRadius(y), gridCenter);
597 g_Map.log("Marking path points");
598 for (let y in cityGridPosition)
599         for (let x in cityGridPosition[y])
600         {
601                 cityGridPosition[y][x].round();
602                 createArea(
603                         new DiskPlacer(pathWidth, cityGridPosition[y][x]),
604                         [
605                                 new TileClassPainter(clPath),
606                                 new TileClassPainter(clPathCrossing)
607                         ]);
608         }
610 g_Map.log("Marking horizontal paths");
611 for (let y = 0; y < gridPointsY; ++y)
612         for (let x = 1; x < gridPointsX; ++x)
613         {
614                 let width = y == gridPointYCenter ? pathWidthSecondary : pathWidth;
615                 createArea(
616                         new PathPlacer(cityGridPosition[y][x - 1], cityGridPosition[y][x], width, 0, 8, 0, 0, Infinity),
617                         new TileClassPainter(clPath));
618         }
620 g_Map.log("Marking vertical paths");
621 for (let y = 1; y < gridPointsY; ++y)
622         for (let x = 0; x < gridPointsX; ++x)
623         {
624                 let width =
625                         Math.abs(x - gridPointXCenter) == 0 ?
626                                 pathWidthCenter :
627                         Math.abs(x - gridPointXCenter) == 1 ?
628                                 pathWidthSecondary :
629                                 pathWidth;
631                 createArea(
632                         new PathPlacer(cityGridPosition[y - 1][x], cityGridPosition[y][x], width, 0, 8, 0, 0, Infinity),
633                         new TileClassPainter(clPath));
634         }
635 Engine.SetProgress(70);
637 g_Map.log("Placing kushite temples");
638 for (let i = 0; i < layoutKushTemples.length; ++i)
640         let x = i + (gridPointsX - layoutKushTemples.length) / 2;
641         let templePosition = Vector2D.add(cityGridPosition[0][x], layoutKushTemples[i].pathOffset.rotate(-Math.PI / 2 - cityGridAngle[0][x]));
642         g_Map.placeEntityPassable(layoutKushTemples[i].template, 0, templePosition, cityGridAngle[0][x]);
643         clTemple.add(templePosition.round());
646 g_Map.log("Placing lion statues in the central path");
647 var statueCount = scaleByMapSize(10, 40);
648 for (let i = 0; i < 2; ++i)
649         for (let stat = 0; stat < statueCount; ++stat)
650         {
651                 let start = new Vector2D(0, pathWidthCenter * 3/4 * (i - 0.5)).rotate(-cityGridAngle[0][gridPointXCenter]).add(cityGridPosition[0][gridPointXCenter]);
652                 let position = new Vector2D(cityGridPosition[gridPointsY - 1][gridPointXCenter].distanceTo(cityGridPosition[0][gridPointXCenter]), 0).mult(stat / statueCount).rotate(-cityGridAngle[0][gridPointXCenter]).add(start).add(new Vector2D(0.5, 0.5));
654                 if (!avoidClasses(clPathCrossing, 2).allows(position))
655                         continue;
657                 g_Map.placeEntityPassable(pickRandom(aStatues), 0, position, cityGridAngle[0][gridPointXCenter] - Math.PI * (i - 0.5));
658                 clPathStatues.add(position.round());
659         }
661 g_Map.log("Placing kushite statues in the secondary paths");
662 for (let x of [gridPointXCenter - 1, gridPointXCenter + 1])
664         g_Map.placeEntityAnywhere(aStatueKush, 0, cityGridPosition[gridPointYCenter][x], cityGridAngle[gridPointYCenter][x]);
665         clPathStatues.add(cityGridPosition[gridPointYCenter][x]);
668 g_Map.log("Painting paths");
669 var areaPaths = createArea(
670         new MapBoundsPlacer(),
671         [
672                 new LayeredPainter([tPathWild, tPath], [1]),
673                 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetPath, 1)
674         ],
675         stayClasses(clPath, 0));
677 g_Map.log("Placing triggerpoints on paths");
678 createObjectGroupsByAreas(
679         new SimpleGroup([new SimpleObject(oTriggerPointPath, 1, 1, 0, 0)], true, clTriggerPointPath),
680         0,
681         [avoidClasses(clTriggerPointPath, 8), stayClasses(clPathCrossing, 2)],
682         scaleByMapSize(20, 100),
683         30,
684         [areaPaths]);
686 g_Map.log("Placing city districts");
687 for (let y = 1; y < gridPointsY; ++y)
688         for (let x = 1; x < gridPointsX; ++x)
689         createArea(
690                 new ConvexPolygonPlacer([cityGridPosition[y - 1][x - 1], cityGridPosition[y - 1][x], cityGridPosition[y][x - 1], cityGridPosition[y][x]], Infinity),
691                 [
692                         new TerrainPainter(tRoadDesert),
693                         new CityPainter(layoutKushCity, (-cityGridAngle[y][x - 1] - cityGridAngle[y][x]) / 2, 0),
694                         new TileClassPainter(clCity)
695                 ],
696                 new StaticConstraint(avoidClasses(clPath, 0)));
698 g_Map.log("Marking path palm area");
699 var areaPathPalms = createArea(
700         new MapBoundsPlacer(),
701         undefined,
702         new StaticConstraint([
703                 new NearTileClassConstraint(clPath, 1),
704                 avoidClasses(clPath, 0, clTemple, 10, clPyramid, 20, clPathCrossing, 1)
705         ]));
707 g_Map.log("Placing path palms");
708 createObjectGroupsByAreas(
709         new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
710         0,
711         avoidClasses(clForest, 2, clCity, 0),
712         scaleByMapSize(100, 400),
713         10,
714         [areaPathPalms]);
716 createBumps(new StaticConstraint(avoidClasses(clPlayer, 6, clCity, 0, clWater, 2, clHill, 0, clPath, 0, clTemple, 8, clPyramid, 8)), scaleByMapSize(30, 300), 1, 8, 4, 0, 3);
717 Engine.SetProgress(75);
719 g_Map.log("Setting up common constraints");
720 const stayDesert = new StaticConstraint(stayClasses(clDesert, 0));
721 const stayFertileLand = new StaticConstraint(stayClasses(clFertileLand, 0));
722 const nearWater = new NearTileClassConstraint(clWater, 3);
723 var avoidCollisions = new AndConstraint(
724         [
725                 new StaticConstraint(avoidClasses(clCliff, 0, clHill, 0, clPlayer, 15, clWater, 1, clPath, 2, clTemple, 12, clPyramid, 7, clCity, 4)),
726                 avoidClasses(clForest, 1, clRock, 4, clMetal, 4, clFood, 6, clSoldier, 1, clTreasure, 1)
727         ]);
729 g_Map.log("Setting up common areas");
730 const areaDesert = createArea(new MapBoundsPlacer(), undefined, stayDesert);
731 const areaFertileLand = createArea(new MapBoundsPlacer(), undefined, stayFertileLand);
733 createForests(
734         [tForestFloorFertile, tForestFloorFertile, tForestFloorFertile, pForestPalms, pForestPalms],
735         [stayFertileLand, avoidClasses(clForest, 15), new StaticConstraint([avoidClasses(clWater, 2), avoidCollisions])],
736         clForest,
737         scaleByMapSize(250, 2000));
739 const avoidCollisionsMines = new StaticConstraint(avoidClasses(
740         clWater, 4, clHill, 0, clFertileLand, 10, clCliff, 4, clCity, 4,
741         clPlayer, 20, clForest, 4, clPyramid, 6, clTemple, 12, clPath, 4));
743 g_Map.log("Creating stone mines");
744 createMines(
745         [
746                 [new SimpleObject(oStoneSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oStoneLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)],
747                 [new SimpleObject(oStoneSmall, 2, 5, 1, 3, 0, 2 * Math.PI, 1)]
748         ],
749         [avoidCollisionsMines, avoidClasses(clRock, 10)],
750         clRock,
751         scaleByMapSize(6, 24));
753 g_Map.log("Creating metal mines");
754 createMines(
755         [
756                 [new SimpleObject(oMetalSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oMetalLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)],
757                 [new SimpleObject(oMetalSmall, 2, 5, 1, 3, 0, 2 * Math.PI, 1)]
758         ],
759         [avoidCollisionsMines, avoidClasses(clMetal, 10, clRock, 5)],
760         clMetal,
761         scaleByMapSize(6, 24));
763 g_Map.log("Creating berries");
764 createObjectGroupsByAreas(
765         new SimpleGroup([new SimpleObject(oBerryBushGrapes, 4, 6, 1, 2)], true, clFood),
766         0,
767         avoidCollisions,
768         scaleByMapSize(3, 15),
769         50,
770         [areaFertileLand]);
772 g_Map.log("Creating rhinos");
773 createObjectGroupsByAreas(
774         new SimpleGroup([new SimpleObject(oRhino, 1, 1, 0, 1)], true, clFood),
775         0,
776         avoidCollisions,
777         scaleByMapSize(2, 10),
778         50,
779         [areaDesert]);
781 g_Map.log("Creating warthogs");
782 createObjectGroupsByAreas(
783         new SimpleGroup([new SimpleObject(oWarthog, 1, 1, 0, 1)], true, clFood),
784         0,
785         avoidCollisions,
786         scaleByMapSize(2, 10),
787         50,
788         [areaFertileLand]);
790 g_Map.log("Creating giraffes");
791 createObjectGroups(
792         new SimpleGroup([new SimpleObject(oGiraffe, 2, 3, 2, 4), new SimpleObject(oGiraffeInfant, 2, 3, 2, 4)], true, clFood),
793         0,
794         avoidCollisions,
795         scaleByMapSize(2, 10),
796         50);
798 g_Map.log("Creating gazelles");
799 createObjectGroups(
800         new SimpleGroup([new SimpleObject(oGazelle, 5, 7, 2, 4)], true, clFood),
801         0,
802         avoidCollisions,
803         scaleByMapSize(2, 10),
804         50,
805         [areaDesert]);
807 if (!isNomad())
809         g_Map.log("Creating lions");
810         createObjectGroupsByAreas(
811                 new SimpleGroup([new SimpleObject(oLion, 1, 2, 2, 4), new SimpleObject(oLioness, 2, 3, 2, 4)], true, clFood),
812                 0,
813                 [avoidCollisions, avoidClasses(clPlayer, 20)],
814                 scaleByMapSize(2, 10),
815                 50,
816                 [areaDesert]);
819 g_Map.log("Creating elephants");
820 createObjectGroupsByAreas(
821         new SimpleGroup([new SimpleObject(oElephant, 2, 3, 2, 4), new SimpleObject(oElephantInfant, 2, 3, 2, 4)], true, clFood),
822         0,
823         avoidCollisions,
824         scaleByMapSize(2, 10),
825         50,
826         [areaDesert]);
828 g_Map.log("Creating crocodiles");
829 if (!isNomad())
830         createObjectGroupsByAreas(
831                 new SimpleGroup([new SimpleObject(oCrocodile, 2, 3, 3, 5)], true, clFood),
832                 0,
833                 [nearWater, avoidCollisions],
834                 scaleByMapSize(1, 6),
835                 50,
836                 [areaFertileLand]);
838 Engine.SetProgress(85);
840 g_Map.log("Marking irrigation canal tree area");
841 var areaIrrigationCanalTrees = createArea(
842         new MapBoundsPlacer(),
843         undefined,
844         [
845                 nearWater,
846                 avoidClasses(clPassage, 3),
847                 avoidCollisions
848         ]);
850 g_Map.log("Creating irrigation canal trees");
851 createObjectGroupsByAreas(
852         new SimpleGroup([new RandomObject(oPalms, 1, 1, 1, 1)], true, clForest),
853         0,
854         avoidClasses(clForest, 1),
855         scaleByMapSize(100, 600),
856         50,
857         [areaIrrigationCanalTrees]);
859 createStragglerTrees(
860         oPalms,
861         [stayFertileLand, avoidCollisions],
862         clForest,
863         scaleByMapSize(50, 400),
864         200);
866 createStragglerTrees(
867         [oAcacia],
868         [stayDesert, avoidCollisions],
869         clForest,
870         scaleByMapSize(50, 400),
871         200);
873 g_Map.log("Placing archer groups on the hilltop");
874 createObjectGroupsByAreas(
875         new SimpleGroup([new RandomObject([oKushCitizenArcher, oKushChampionArcher], scaleByMapSize(4, 10), scaleByMapSize(6, 20), 1, 4)], true, clSoldier),
876         0,
877         new StaticConstraint([avoidClasses(clCliff, 1), new NearTileClassConstraint(clCliff, 5)]),
878         scaleByMapSize(1, 5),
879         250,
880         [areaHilltop]);
882 g_Map.log("Placing individual archers on the hill");
883 createObjectGroupsByAreas(
884         new SimpleGroup([new RandomObject([oKushCitizenArcher, oKushChampionArcher], 1, 1, 1, 3)], true, clSoldier),
885         0,
886         new StaticConstraint([
887                 new HeightConstraint(heightHillArchers, heightHilltop),
888                 avoidClasses(clCliff, 1, clSoldier, 1),
889                 new NearTileClassConstraint(clCliff, 5)
890         ]),
891         scaleByMapSize(8, 100),
892         250,
893         [areaHill]);
895 g_Map.log("Placing siege engines on the hilltop");
896 createObjectGroupsByAreas(
897         new SimpleGroup([new RandomObject(oPtolSiege, 1, 1, 1, 3)], true, clSoldier),
898         0,
899         new StaticConstraint([new NearTileClassConstraint(clCliff, 5), avoidClasses(clCliff, 1, clSoldier, 1)]),
900         scaleByMapSize(1, 6),
901         250,
902         [areaHilltop]);
904 g_Map.log("Placing soldiers near pyramids");
905 const avoidCollisionsPyramids = new StaticConstraint([avoidCollisions, new NearTileClassConstraint(clPyramid, 10)]);
906 createObjectGroupsByAreas(
907         new SimpleGroup([new SimpleObject(oKushCitizenArcher, 1, 1, 1, 1)], true, clSoldier),
908         0,
909         avoidCollisionsPyramids,
910         scaleByMapSize(3, 8),
911         250,
912         [areaPyramids]);
914 g_Map.log("Placing treasures at the pyramid");
915 createObjectGroupsByAreas(
916         new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
917         0,
918         avoidCollisionsPyramids,
919         scaleByMapSize(1, 10),
920         250,
921         [areaPyramids]);
923 g_Map.log("Placing treasures on the hilltop");
924 createObjectGroupsByAreas(
925         new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
926         0,
927         new StaticConstraint([avoidClasses(clCliff, 1, clTreasure, 1)]),
928         scaleByMapSize(3, 10),
929         250,
930         [areaHilltop]);
932 g_Map.log("Placing treasures in the city");
933 var pathBorderConstraint = [new StaticConstraint([new NearTileClassConstraint(clCity, 1)]), avoidClasses(clTreasure, 2, clStatue, 10, clPathStatues, 4)];
934 createObjectGroupsByAreas(
935         new SimpleGroup([new RandomObject(oTreasuresCity, 1, 1, 2, 2)], true, clTreasure),
936         0,
937         pathBorderConstraint,
938         scaleByMapSize(2, 40),
939         250,
940         [areaPaths]);
942 g_Map.log("Placing handcarts on the paths");
943 createObjectGroupsByAreas(
944         new SimpleGroup([new SimpleObject(aHandcart, 1, 1, 1, 1)], true),
945         0,
946         pathBorderConstraint,
947         scaleByMapSize(0, 5),
948         250,
949         [areaPaths]);
951 g_Map.log("Placing fence in fertile land");
952 createObjectGroupsByAreas(
953         new SimpleGroup([new SimpleObject(aPlotFence, 1, 1, 1, 1)], true),
954         0,
955         new StaticConstraint(avoidCollisions, avoidClasses(clWater, 4)),
956         scaleByMapSize(1, 10),
957         250,
958         [areaFertileLand]);
960 g_Map.log("Creating fish");
961 createObjectGroups(
962         new SimpleGroup([new SimpleObject(oFish, 3, 4, 2, 3)], true, clFood),
963         0,
964         [new StaticConstraint(stayClasses(clWater, 6)), avoidClasses(clFood, 12)],
965         scaleByMapSize(20, 120),
966         50);
968 Engine.SetProgress(95);
970 avoidCollisions = new StaticConstraint(avoidCollisions);
972 createDecoration(
973         aBushesDesert.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
974         aBushesDesert.map(bush => scaleByMapSize(20, 150) * randIntInclusive(1, 3)),
975         [stayDesert, avoidCollisions]);
977 createDecoration(
978         aBushesFertileLand.map(bush => [new SimpleObject(bush, 0, 4, 2, 4)]),
979         aBushesFertileLand.map(bush => scaleByMapSize(20, 150) * randIntInclusive(1, 3)),
980         [stayFertileLand, avoidCollisions]);
982 createDecoration(
983         [[new SimpleObject(aRock, 0, 4, 2, 4)]],
984         [[scaleByMapSize(80, 500)]],
985         [stayDesert, avoidCollisions]);
987 createDecoration(
988         aBushesFertileLand.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
989         aBushesFertileLand.map(bush => scaleByMapSize(100, 800)),
990         [new HeightConstraint(heightWaterLevel, heightShoreline), avoidCollisions]);
992 g_Map.log("Creating reeds");
993 createObjectGroupsByAreas(
994         new SimpleGroup([new RandomObject(aWaterDecoratives, 2, 4, 1, 2)], true),
995         0,
996         new StaticConstraint(new NearTileClassConstraint(clFertileLand, 4)),
997         scaleByMapSize(50, 400),
998         20,
999         [areaWater]);
1001 g_Map.log("Creating hawk");
1002 for (let i = 0; i < scaleByMapSize(0, 2); ++i)
1003         g_Map.placeEntityAnywhere(oHawk, 0, mapCenter, randomAngle());
1005 placePlayersNomad(clPlayer, [stayClasses(clFertileLand, 0), avoidClasses(clCity, 15), avoidCollisions]);
1007 setWindAngle(-0.43);
1008 setWaterHeight(heightWaterLevel + SEA_LEVEL);
1009 setWaterTint(0.161, 0.286, 0.353);
1010 setWaterColor(0.129, 0.176, 0.259);
1011 setWaterWaviness(8);
1012 setWaterMurkiness(0.87);
1013 setWaterType("lake");
1015 setTerrainAmbientColor(0.58, 0.443, 0.353);
1017 setSunColor(0.733, 0.746, 0.574);
1018 setSunRotation(Math.PI / 2 * randFloat(-1, 1));
1019 setSunElevation(Math.PI / 7);
1021 setFogFactor(0);
1022 setFogThickness(0);
1023 setFogColor(0.69, 0.616, 0.541);
1025 setPPEffect("hdr");
1026 setPPContrast(0.67);
1027 setPPSaturation(0.42);
1028 setPPBloom(0.23);
1030 g_Map.ExportMap();