2 * For historic reference, see http://www.jebelbarkal.org/images/maps/siteplan.jpg
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";
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 = [
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");
96 "props/structures/kushites/statue_pedestal_rectangular",
97 "props/structures/kushites/statue_pedestal_rectangular_lion"
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",
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 = [
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));
187 const pathWidthCenter = 10;
188 const pathWidthSecondary = 6;
190 const placeNapataWall = getDifficulty() >= 3;
192 const layoutFertileLandTextures = [
194 "left": fractionToTiles(0),
195 "right": fractionToTiles(0.04),
196 "terrain": createTerrain(tGrassTransition1),
197 "tileClass": clFertileLand
200 "left": fractionToTiles(0.04),
201 "right": fractionToTiles(0.08),
202 "terrain": createTerrain(tGrassTransition2),
203 "tileClass": clDesert
207 var layoutKushTemples = [
208 ...new Array(2).fill(0).map((v, i) =>
210 "template": oTempleApedemak,
211 "pathOffset": new Vector2D(0, 9),
212 "minMapSize": i == 0 ? 320 : 0
215 "template": oTempleAmun,
216 "pathOffset": new Vector2D(0, 12),
220 "template": oWonderPtol,
221 "pathOffset": new Vector2D(0, scaleByMapSize(9, 14)),
225 "template": oTempleAmun,
226 "pathOffset": new Vector2D(0, 12),
229 ...new Array(2).fill(0).map((v, i) =>
231 "template": oTempleApedemak,
232 "pathOffset": new Vector2D(0, 9),
233 "minMapSize": i == 0 ? 0 : 320
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.
241 const layoutKushCity = [
243 "templateName": "uncapturable|" + oHouse,
244 "difficulty": "Very Easy",
245 "painters": new TileClassPainter(clHouse)
248 "templateName": "uncapturable|" + oFortress,
249 "difficulty": "Medium",
250 "constraints": [avoidClasses(clFortress, 25), new NearTileClassConstraint(clPath, 8)],
251 "painters": new TileClassPainter(clFortress)
254 "templateName": "uncapturable|" + oCivicCenter,
255 "difficulty": "Easy",
256 "constraints": [avoidClasses(clCivicCenter, 60), new NearTileClassConstraint(clPath, 8)],
257 "painters": new TileClassPainter(clCivicCenter)
260 "templateName": "uncapturable|" + oElephantStables,
261 "difficulty": "Medium",
262 "constraints": avoidClasses(clElephantStables, 10),
263 "painters": new TileClassPainter(clElephantStables)
266 "templateName": "uncapturable|" + oStable,
267 "difficulty": "Easy",
268 "constraints": avoidClasses(clStable, 20),
269 "painters": new TileClassPainter(clStable)
272 "templateName": "uncapturable|" + oBarracks,
273 "difficulty": "Easy",
274 "constraints": avoidClasses(clBarracks, 12),
275 "painters": new TileClassPainter(clBarracks)
278 "templateName": "uncapturable|" + oTower,
279 "difficulty": "Medium",
280 "constraints": avoidClasses(clTower, 17),
281 "painters": new TileClassPainter(clTower)
284 "templateName": "uncapturable|" + oMarket,
285 "difficulty": "Very Easy",
286 "constraints": avoidClasses(clMarket, 15),
287 "painters": new TileClassPainter(clMarket)
290 "templateName": "uncapturable|" + oBlacksmith,
291 "difficulty": "Very Easy",
292 "constraints": avoidClasses(clBlacksmith, 30),
293 "painters": new TileClassPainter(clBlacksmith)
296 "templateName": "uncapturable|" + oNubaVillage,
297 "difficulty": "Easy",
298 "constraints": avoidClasses(clNubaVillage, 30),
299 "painters": new TileClassPainter(clNubaVillage)
302 "templateName": "uncapturable|" + oBlemmyeCamp,
303 "difficulty": "Easy",
304 "constraints": avoidClasses(clBlemmyeCamp, 30),
305 "painters": new TileClassPainter(clBlemmyeCamp)
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),
317 Engine.SetProgress(10);
319 g_Map.log("Loading hill heightmap");
321 new MapBoundsPlacer(),
322 new HeightmapPainter(
324 new Vector2D(-12, scaleByMapSize(-12, -25)),
326 convertHeightmap1Dto2D(Engine.LoadMapTerrain("maps/random/jebel_barkal.pmp").height)),
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");
345 new MapBoundsPlacer(),
346 new ElevationPainter(heightDesert),
347 new HeightConstraint(-Infinity, heightDesert));
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),
357 "heightLand": heightDesert,
358 "heightRiverbed": heightFertileLand,
361 "waterFunc": (position, height, riverFraction) => {
362 createTerrain(tGrass).place(position);
363 clFertileLand.add(position);
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)
371 riv.tileClass.add(position);
372 riv.terrain.place(position);
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),
385 "heightLand": heightFertileLand,
386 "heightRiverbed": heightSeaGround,
390 Engine.SetProgress(30);
392 g_Map.log("Computing player locations");
393 const playerIDs = primeSortAllPlayers();
394 const playerPosition = playerPlacementCustomAngle(
395 fractionToTiles(0.38),
397 i => Math.PI * (-0.42 / numPlayers * (i + i % 2) - (i % 2) / 2))[0];
401 g_Map.log("Marking player positions");
402 for (let position of playerPosition)
403 addCivicCenterAreaToClass(position, clPlayer);
406 g_Map.log("Marking water");
408 new MapBoundsPlacer(),
410 new TileClassPainter(clWater),
411 new TileClassUnPainter(clFertileLand)
413 new HeightConstraint(-Infinity, heightWaterLevel));
415 g_Map.log("Marking desert");
417 new MapBoundsPlacer(),
418 new TileClassPainter(clDesert),
420 new HeightConstraint(-Infinity, heightHill),
421 avoidClasses(clWater, 0, clFertileLand, 0)
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(
432 new Vector2D(x, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
433 new Vector2D(x, mapBounds.top).rotateAround(-riverAngle, mapCenter),
441 avoidClasses(clDesert, 2)));
444 g_Map.log("Creating irrigation canals");
445 var irrigationCanalLocations = [];
446 for (let area of irrigationCanalAreas)
448 if (!area.getPoints().length ||
449 area.getPoints().some(point => !avoidClasses(clPlayer, scaleByMapSize(8, 13), clIrrigationCanal, scaleByMapSize(15, 25)).allows(point)))
452 irrigationCanalLocations.push(pickRandom(area.getPoints()).clone().rotateAround(riverAngle, mapCenter).x);
454 new MapBoundsPlacer(),
456 new SmoothElevationPainter(ELEVATION_SET, heightIrrigationCanal, 1),
457 new TileClassPainter(clIrrigationCanal)
459 [new StayAreasConstraint([area]), new HeightConstraint(heightIrrigationCanal, heightDesert)]);
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;
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)
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))
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,
497 Engine.SetProgress(40);
499 g_Map.log("Marking hill");
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");
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),
528 new HeightConstraint(heightHilltop, Infinity),
529 new SlopeConstraint(-Infinity, 2)
532 Engine.SetProgress(50);
534 for (let i = 0; i < numPlayers; ++i)
536 let isDesert = clDesert.has(playerPosition[i]);
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",
545 "outerTerrain": isDesert ? tRoadDesert : tRoadFertileLand,
546 "innerTerrain": isDesert ? tRoadDesert : tRoadFertileLand
549 "template": oGazelle,
551 "minGroupDistance": 2,
552 "maxGroupDistance": 4,
557 "template": isDesert ? oBerryBushDesert : oBerryBushGrapes
561 { "template": oMetalLarge },
562 { "template": oStoneLarge }
566 "template": isDesert ? oAcacia : pickRandom(oPalms),
567 "count": isDesert ? scaleByMapSize(5, 10) : scaleByMapSize(15, 30)
573 "template": oWoodTreasure,
574 "count": isDesert ? 4 : 0
577 "template": oStoneTreasure,
578 "count": isDesert ? 1 : 0
581 "template": oMetalTreasure,
582 "count": isDesert ? 1 : 0
587 "template": isDesert ? aRock : pickRandom(aBushesFertileLand)
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(
598 ["uncapturable|" + oPyramidLarge, "uncapturable|" + oPyramidSmall],
599 scaleByMapSize(1, 6),
600 scaleByMapSize(2, 8),
601 scaleByMapSize(6, 8),
602 scaleByMapSize(6, 14),
605 scaleByMapSize(6, 8))],
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])
639 cityGridPosition[y][x].round();
641 new DiskPlacer(pathWidth, cityGridPosition[y][x]),
643 new TileClassPainter(clPath),
644 new TileClassPainter(clPathCrossing)
648 g_Map.log("Marking horizontal city paths");
649 for (let y = 0; y < gridPointsY; ++y)
650 for (let x = 1; x < gridPointsX; ++x)
652 let width = y == gridPointYCenter ? pathWidthSecondary : pathWidth;
654 new PathPlacer(cityGridPosition[y][x - 1], cityGridPosition[y][x], width, 0, 8, 0, 0, Infinity),
655 new TileClassPainter(clPath));
658 g_Map.log("Marking vertical city paths");
659 for (let y = 1; y < gridPointsY; ++y)
660 for (let x = 0; x < gridPointsX; ++x)
663 Math.abs(x - gridPointXCenter) == 0 ?
665 Math.abs(x - gridPointXCenter) == 1 ?
670 new PathPlacer(cityGridPosition[y - 1][x], cityGridPosition[y][x], width, 0, 8, 0, 0, Infinity),
671 new TileClassPainter(clPath));
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");
686 new EntitiesObstructionPlacer(entitiesTemples, 0, Infinity),
687 new TileClassPainter(clTemple));
689 g_Map.log("Smoothing temple ground");
691 new MapBoundsPlacer(),
692 new ElevationBlendingPainter(heightDesert, 0.8),
693 new NearTileClassConstraint(clTemple, 0));
695 g_Map.log("Painting cliffs");
697 new MapBoundsPlacer(),
699 new TerrainPainter(tHillCliff),
700 new TileClassPainter(clCliff)
703 stayClasses(clHill, 0),
704 new SlopeConstraint(2, Infinity)
707 g_Map.log("Painting temple ground");
709 new MapBoundsPlacer(),
710 new TerrainPainter(tPathWild),
712 new NearTileClassConstraint(clTemple, 1),
713 avoidClasses(clPath, 0, clCliff, 0)
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)
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))
730 g_Map.placeEntityPassable(pickRandom(aStatues), 0, position, centralPathAngle - Math.PI * (i + 0.5));
731 clPathStatues.add(position.round());
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)
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))
745 g_Map.placeEntityPassable(pickRandom(oKushChampions), 0, position, centralPathAngle - Math.PI * (i - 0.5));
746 clPathStatues.add(position.round());
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(),
760 new LayeredPainter([tPathWild, tPath], [1]),
761 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetPath, 1)
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),
769 [avoidClasses(clTriggerPointCityPath, 8), stayClasses(clPathCrossing, 2)],
770 scaleByMapSize(20, 100),
774 g_Map.log("Placing city districts");
775 for (let y = 1; y < gridPointsY; ++y)
776 for (let x = 1; x < gridPointsX; ++x)
778 new ConvexPolygonPlacer([cityGridPosition[y - 1][x - 1], cityGridPosition[y - 1][x], cityGridPosition[y][x - 1], cityGridPosition[y][x]], Infinity),
780 new TerrainPainter(tRoadDesert),
781 new CityPainter(layoutKushCity, (-cityGridAngle[y][x - 1] - cityGridAngle[y][x]) / 2, 0),
782 new TileClassPainter(clCity)
784 new StaticConstraint(avoidClasses(clPath, 0)));
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(
796 ["tower", "short", "tower", "gate", "tower", "medium", "tower", "short"],
800 wallGridMaxAngleFront,
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(
814 wallGridPosition[x - 1],
816 ["tower", "gate", "tower", "short", "tower", "short", "tower"],
820 avoidClasses(clHill, 0, clTemple, 0)));
822 g_Map.log("Marking walls");
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));
830 new EntitiesObstructionPlacer(entitiesGates, 0, Infinity),
831 new TileClassPainter(clGate));
833 g_Map.log("Painting wall terrain");
835 new MapBoundsPlacer(),
837 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetWalls, 2),
838 new TerrainPainter(tPathWild)
841 new NearTileClassConstraint(clWall, 1),
842 avoidClasses(clCliff, 0)
845 Engine.SetProgress(70);
847 g_Map.log("Marking city palm area");
850 new MapBoundsPlacer(),
852 new StaticConstraint([
853 new NearTileClassConstraint(clPath, 1),
864 clElephantStables, 1,
873 g_Map.log("Placing city palms");
874 createObjectGroupsByAreas(
875 new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
877 avoidClasses(clForest, 2),
878 scaleByMapSize(40, 400),
884 g_Map.log("Marking wall palm area");
885 var areaWallPalms = createArea(
886 new MapBoundsPlacer(),
888 new StaticConstraint([
889 new NearTileClassConstraint(clWall, 2),
890 avoidClasses(clPath, 1, clWall, 1, clGate, 3, clTemple, 2, clHill, 6)
893 g_Map.log("Placing city palms");
894 createObjectGroupsByAreas(
895 new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
897 avoidClasses(clForest, 2),
898 scaleByMapSize(40, 200),
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(
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)
916 g_Map.log("Setting up common areas");
917 const areaDesert = createArea(new MapBoundsPlacer(), undefined, stayDesert);
918 const areaFertileLand = createArea(new MapBoundsPlacer(), undefined, stayFertileLand);
921 [tForestFloorFertile, tForestFloorFertile, tForestFloorFertile, pForestPalms, pForestPalms],
922 [stayFertileLand, avoidClasses(clForest, 15), new StaticConstraint([avoidClasses(clWater, 2), avoidCollisions])],
924 scaleByMapSize(250, 2000));
926 const avoidCollisionsMines = new StaticConstraint([
927 isNomad() ? new NullConstraint() : avoidClasses(clFertileLand, 10),
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");
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)]
938 [avoidCollisionsMines, avoidClasses(clRock, 10)],
940 scaleByMapSize(8, 26));
942 g_Map.log("Creating metal mines");
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)]
948 [avoidCollisionsMines, avoidClasses(clMetal, 10, clRock, 5)],
950 scaleByMapSize(8, 26));
952 g_Map.log("Placing triggerpoints for attackers");
954 new SimpleGroup([new SimpleObject(oTriggerPointAttackerPatrol, 1, 1, 0, 0)], true, clTriggerPointMap),
956 [avoidClasses(clCity, 8, clCliff, 4, clHill, 4, clWater, 0, clWall, 2, clForest, 1, clRock, 4, clMetal, 4, clTriggerPointMap, 15)],
957 scaleByMapSize(20, 100),
960 g_Map.log("Creating berries");
961 createObjectGroupsByAreas(
962 new SimpleGroup([new SimpleObject(oBerryBushGrapes, 4, 6, 1, 2)], true, clFood),
965 scaleByMapSize(3, 15),
969 g_Map.log("Creating rhinos");
970 createObjectGroupsByAreas(
971 new SimpleGroup([new SimpleObject(oRhino, 1, 1, 0, 1)], true, clFood),
974 scaleByMapSize(2, 10),
978 g_Map.log("Creating warthogs");
979 createObjectGroupsByAreas(
980 new SimpleGroup([new SimpleObject(oWarthog, 1, 1, 0, 1)], true, clFood),
983 scaleByMapSize(2, 10),
987 g_Map.log("Creating giraffes");
989 new SimpleGroup([new SimpleObject(oGiraffe, 2, 3, 2, 4), new SimpleObject(oGiraffeInfant, 2, 3, 2, 4)], true, clFood),
992 scaleByMapSize(2, 10),
995 g_Map.log("Creating gazelles");
997 new SimpleGroup([new SimpleObject(oGazelle, 5, 7, 2, 4)], true, clFood),
1000 scaleByMapSize(2, 10),
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),
1010 [avoidCollisions, avoidClasses(clPlayer, 20)],
1011 scaleByMapSize(2, 10),
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),
1021 scaleByMapSize(2, 10),
1025 g_Map.log("Creating crocodiles");
1027 createObjectGroupsByAreas(
1028 new SimpleGroup([new SimpleObject(oCrocodile, 2, 3, 3, 5)], true, clFood),
1030 [nearWater, avoidCollisions],
1031 scaleByMapSize(1, 6),
1035 Engine.SetProgress(85);
1037 g_Map.log("Marking irrigation canal tree area");
1038 var areaIrrigationCanalTrees = createArea(
1039 new MapBoundsPlacer(),
1043 avoidClasses(clPassage, 3),
1047 g_Map.log("Creating irrigation canal trees");
1048 createObjectGroupsByAreas(
1049 new SimpleGroup([new RandomObject(oPalms, 1, 1, 1, 1)], true, clForest),
1051 avoidClasses(clForest, 1),
1052 scaleByMapSize(100, 600),
1054 [areaIrrigationCanalTrees]);
1056 createStragglerTrees(
1058 [stayFertileLand, avoidCollisions],
1060 scaleByMapSize(50, 400),
1063 createStragglerTrees(
1065 [stayDesert, avoidCollisions],
1067 scaleByMapSize(50, 400),
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),
1074 new StaticConstraint([avoidClasses(clCliff, 1), new NearTileClassConstraint(clCliff, 5)]),
1075 scaleByMapSize(1, 5),
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),
1083 new StaticConstraint([
1084 new HeightConstraint(heightHillArchers, heightHilltop),
1085 avoidClasses(clCliff, 1, clSoldier, 1),
1086 new NearTileClassConstraint(clCliff, 5)
1088 scaleByMapSize(8, 100),
1092 g_Map.log("Placing siege engines on the hilltop");
1093 createObjectGroupsByAreas(
1094 new SimpleGroup([new RandomObject(oPtolSiege, 1, 1, 1, 3)], true, clSoldier),
1096 new StaticConstraint([new NearTileClassConstraint(clCliff, 5), avoidClasses(clCliff, 1, clSoldier, 1)]),
1097 scaleByMapSize(1, 6),
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),
1106 avoidCollisionsPyramids,
1107 scaleByMapSize(3, 8),
1111 g_Map.log("Placing treasures at the pyramid");
1112 createObjectGroupsByAreas(
1113 new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
1115 avoidCollisionsPyramids,
1116 scaleByMapSize(1, 10),
1120 g_Map.log("Placing treasures on the hilltop");
1121 createObjectGroupsByAreas(
1122 new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
1124 new StaticConstraint([avoidClasses(clCliff, 1, clTreasure, 1)]),
1125 scaleByMapSize(8, 35),
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),
1137 pathBorderConstraint,
1138 scaleByMapSize(2, 60),
1142 g_Map.log("Placing handcarts on the paths");
1143 createObjectGroupsByAreas(
1144 new SimpleGroup([new SimpleObject(aHandcart, 1, 1, 1, 1)], true, clDecorative),
1146 [pathBorderConstraint, avoidClasses(clDecorative, 10)],
1147 scaleByMapSize(0, 5),
1151 g_Map.log("Placing fence in fertile land");
1152 createObjectGroupsByAreas(
1153 new SimpleGroup([new SimpleObject(aPlotFence, 1, 1, 1, 1)], true, clDecorative),
1155 new StaticConstraint(avoidCollisions, avoidClasses(clWater, 6, clDecorative, 10)),
1156 scaleByMapSize(1, 10),
1160 g_Map.log("Creating fish");
1162 new SimpleGroup([new SimpleObject(oFish, 3, 4, 2, 3)], true, clFood),
1164 [new StaticConstraint(stayClasses(clWater, 6)), avoidClasses(clFood, 12)],
1165 scaleByMapSize(20, 120),
1168 Engine.SetProgress(95);
1170 avoidCollisions = new StaticConstraint(avoidCollisions);
1173 aBushesDesert.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
1174 aBushesDesert.map(bush => scaleByMapSize(20, 150) * randIntInclusive(1, 3)),
1175 [stayDesert, avoidCollisions]);
1178 aBushesFertileLand.map(bush => [new SimpleObject(bush, 0, 4, 2, 4)]),
1179 aBushesFertileLand.map(bush => scaleByMapSize(20, 150) * randIntInclusive(1, 3)),
1180 [stayFertileLand, avoidCollisions]);
1183 [[new SimpleObject(aRock, 0, 4, 2, 4)]],
1184 [[scaleByMapSize(80, 500)]],
1185 [stayDesert, avoidCollisions]);
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),
1196 new StaticConstraint(new NearTileClassConstraint(clFertileLand, 4)),
1197 scaleByMapSize(50, 400),
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);
1223 setFogColor(0.69, 0.616, 0.541);
1226 setPPContrast(0.67);
1227 setPPSaturation(0.42);