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 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");
86 "props/structures/kushites/statue_lion",
87 "props/structures/kushites/statue_ram"
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",
95 "props/flora/foliagebush",
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 = [
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));
172 const pathWidthCenter = 10;
173 const pathWidthSecondary = 6;
175 const layoutFertileLandTextures = [
177 "left": fractionToTiles(0),
178 "right": fractionToTiles(0.04),
179 "terrain": createTerrain(tGrassTransition1),
180 "tileClass": clFertileLand
183 "left": fractionToTiles(0.04),
184 "right": fractionToTiles(0.08),
185 "terrain": createTerrain(tGrassTransition2),
186 "tileClass": clDesert
190 var layoutKushTemples = [
191 ...new Array(2).fill(0).map((v, i) =>
193 "template": oTempleApedemak,
194 "pathOffset": new Vector2D(0, 9),
195 "minMapSize": i == 0 ? 320 : 0
198 "template": oTempleAmun,
199 "pathOffset": new Vector2D(0, 12),
203 "template": oWonderPtol,
204 "pathOffset": new Vector2D(0, scaleByMapSize(9, 14)),
208 "template": oTempleAmun,
209 "pathOffset": new Vector2D(0, 12),
212 ...new Array(2).fill(0).map((v, i) =>
214 "template": oTempleApedemak,
215 "pathOffset": new Vector2D(0, 9),
216 "minMapSize": i == 0 ? 0 : 320
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.
224 const layoutKushCity = [
226 "templateName": "uncapturable|" + oHouse
229 "templateName": "uncapturable|" + oFortress,
230 "constraints": [avoidClasses(clFortress, 25), new NearTileClassConstraint(clPath, 8)],
231 "painters": new TileClassPainter(clFortress)
234 "templateName": "uncapturable|" + oCivicCenter,
235 "constraints": [avoidClasses(clCivicCenter, 60), new NearTileClassConstraint(clPath, 8)],
236 "painters": new TileClassPainter(clCivicCenter)
239 "templateName": "uncapturable|" + oElephantStables,
240 "constraints": avoidClasses(clElephantStables, 10),
241 "painters": new TileClassPainter(clElephantStables)
244 "templateName": "uncapturable|" + oBarracks,
245 "constraints": avoidClasses(clBarracks, 12),
246 "painters": new TileClassPainter(clBarracks)
249 "templateName": "uncapturable|" + oTower,
250 "constraints": avoidClasses(clTower, 17),
251 "painters": new TileClassPainter(clTower)
254 "templateName": "uncapturable|" + oMarket,
255 "constraints": avoidClasses(clMarket, 15),
256 "painters": new TileClassPainter(clMarket)
259 "templateName": "uncapturable|" + oBlacksmith,
260 "constraints": avoidClasses(clBlacksmith, 30),
261 "painters": new TileClassPainter(clBlacksmith)
264 "templateName": "uncapturable|" + oNubaVillage,
265 "constraints": avoidClasses(clNubaVillage, 30),
266 "painters": new TileClassPainter(clNubaVillage)
269 "templateName": "uncapturable|" + oBlemmyeCamp,
270 "constraints": avoidClasses(clBlemmyeCamp, 30),
271 "painters": new TileClassPainter(clBlemmyeCamp)
274 Engine.SetProgress(10);
276 g_Map.log("Loading hill heightmap");
278 new MapBoundsPlacer(),
279 new HeightmapPainter(
281 new Vector2D(-12, scaleByMapSize(-12, -25)),
283 convertHeightmap1Dto2D(Engine.LoadMapTerrain("maps/random/jebel_barkal.pmp").height)),
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");
301 new MapBoundsPlacer(),
302 new ElevationPainter(heightDesert),
303 new HeightConstraint(-Infinity, heightDesert));
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),
313 "heightLand": heightDesert,
314 "heightRiverbed": heightFertileLand,
317 "waterFunc": (position, height, riverFraction) => {
318 createTerrain(tGrass).place(position);
319 clFertileLand.add(position);
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)
327 riv.tileClass.add(position);
328 riv.terrain.place(position);
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),
341 "heightLand": heightFertileLand,
342 "heightRiverbed": heightSeaGround,
346 Engine.SetProgress(30);
348 g_Map.log("Computing player locations");
349 const playerIDs = primeSortAllPlayers();
350 const playerPosition = playerPlacementCustomAngle(
351 fractionToTiles(0.38),
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");
361 new MapBoundsPlacer(),
363 new TileClassPainter(clWater),
364 new TileClassUnPainter(clFertileLand)
366 new HeightConstraint(-Infinity, heightWaterLevel));
368 g_Map.log("Marking desert");
370 new MapBoundsPlacer(),
371 new TileClassPainter(clDesert),
373 new HeightConstraint(-Infinity, heightHill),
374 avoidClasses(clWater, 0, clFertileLand, 0)
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(
385 new Vector2D(x, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
386 new Vector2D(x, mapBounds.top).rotateAround(-riverAngle, mapCenter),
394 avoidClasses(clDesert, 2)));
397 g_Map.log("Creating irrigation canals");
398 var irrigationCanalLocations = [];
399 for (let area of irrigationCanalAreas)
401 if (!area.getPoints().length ||
402 area.getPoints().some(point => !avoidClasses(clPlayer, scaleByMapSize(8, 13), clIrrigationCanal, scaleByMapSize(15, 25)).allows(point)))
405 irrigationCanalLocations.push(pickRandom(area.getPoints()).clone().rotateAround(riverAngle, mapCenter).x);
407 new MapBoundsPlacer(),
409 new SmoothElevationPainter(ELEVATION_SET, heightIrrigationCanal, 1),
410 new TileClassPainter(clIrrigationCanal)
412 [new StayAreasConstraint([area]), new HeightConstraint(heightIrrigationCanal, heightDesert)]);
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;
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)
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))
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,
450 Engine.SetProgress(40);
452 g_Map.log("Marking hill");
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");
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),
481 new HeightConstraint(heightHilltop, Infinity),
482 new SlopeConstraint(-Infinity, 2)
485 g_Map.log("Painting cliffs");
487 new MapBoundsPlacer(),
489 new TerrainPainter(tHillCliff),
490 new TileClassPainter(clCliff)
493 stayClasses(clHill, 0),
494 new SlopeConstraint(2, Infinity)
496 Engine.SetProgress(50);
498 for (let i = 0; i < playerIDs.length; ++i)
500 let isDesert = clDesert.has(playerPosition[i]);
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",
509 "outerTerrain": isDesert ? tRoadDesert : tRoadFertileLand,
510 "innerTerrain": isDesert ? tRoadDesert : tRoadFertileLand
513 "template": oGazelle,
515 "minGroupDistance": 2,
516 "maxGroupDistance": 4,
521 "template": isDesert ? oBerryBushDesert : oBerryBushGrapes
525 { "template": oMetalLarge },
526 { "template": oStoneLarge }
530 "template": isDesert ? oAcacia : pickRandom(oPalms),
531 "count": isDesert ? scaleByMapSize(5, 10) : scaleByMapSize(15, 30)
537 "template": oWoodTreasure,
538 "count": isDesert ? 3 : 0
541 "template": oStoneTreasure,
542 "count": isDesert ? 1 : 0
545 "template": oMetalTreasure,
546 "count": isDesert ? 1 : 0
551 "template": isDesert ? aRock : pickRandom(aBushesFertileLand)
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(
562 ["uncapturable|" + oPyramidLarge, "uncapturable|" + oPyramidSmall],
563 scaleByMapSize(1, 6),
564 scaleByMapSize(2, 8),
565 scaleByMapSize(6, 8),
566 scaleByMapSize(6, 14),
569 scaleByMapSize(6, 8))],
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])
601 cityGridPosition[y][x].round();
603 new DiskPlacer(pathWidth, cityGridPosition[y][x]),
605 new TileClassPainter(clPath),
606 new TileClassPainter(clPathCrossing)
610 g_Map.log("Marking horizontal paths");
611 for (let y = 0; y < gridPointsY; ++y)
612 for (let x = 1; x < gridPointsX; ++x)
614 let width = y == gridPointYCenter ? pathWidthSecondary : pathWidth;
616 new PathPlacer(cityGridPosition[y][x - 1], cityGridPosition[y][x], width, 0, 8, 0, 0, Infinity),
617 new TileClassPainter(clPath));
620 g_Map.log("Marking vertical paths");
621 for (let y = 1; y < gridPointsY; ++y)
622 for (let x = 0; x < gridPointsX; ++x)
625 Math.abs(x - gridPointXCenter) == 0 ?
627 Math.abs(x - gridPointXCenter) == 1 ?
632 new PathPlacer(cityGridPosition[y - 1][x], cityGridPosition[y][x], width, 0, 8, 0, 0, Infinity),
633 new TileClassPainter(clPath));
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)
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))
657 g_Map.placeEntityPassable(pickRandom(aStatues), 0, position, cityGridAngle[0][gridPointXCenter] - Math.PI * (i - 0.5));
658 clPathStatues.add(position.round());
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(),
672 new LayeredPainter([tPathWild, tPath], [1]),
673 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetPath, 1)
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),
681 [avoidClasses(clTriggerPointPath, 8), stayClasses(clPathCrossing, 2)],
682 scaleByMapSize(20, 100),
686 g_Map.log("Placing city districts");
687 for (let y = 1; y < gridPointsY; ++y)
688 for (let x = 1; x < gridPointsX; ++x)
690 new ConvexPolygonPlacer([cityGridPosition[y - 1][x - 1], cityGridPosition[y - 1][x], cityGridPosition[y][x - 1], cityGridPosition[y][x]], Infinity),
692 new TerrainPainter(tRoadDesert),
693 new CityPainter(layoutKushCity, (-cityGridAngle[y][x - 1] - cityGridAngle[y][x]) / 2, 0),
694 new TileClassPainter(clCity)
696 new StaticConstraint(avoidClasses(clPath, 0)));
698 g_Map.log("Marking path palm area");
699 var areaPathPalms = createArea(
700 new MapBoundsPlacer(),
702 new StaticConstraint([
703 new NearTileClassConstraint(clPath, 1),
704 avoidClasses(clPath, 0, clTemple, 10, clPyramid, 20, clPathCrossing, 1)
707 g_Map.log("Placing path palms");
708 createObjectGroupsByAreas(
709 new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
711 avoidClasses(clForest, 2, clCity, 0),
712 scaleByMapSize(100, 400),
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(
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)
729 g_Map.log("Setting up common areas");
730 const areaDesert = createArea(new MapBoundsPlacer(), undefined, stayDesert);
731 const areaFertileLand = createArea(new MapBoundsPlacer(), undefined, stayFertileLand);
734 [tForestFloorFertile, tForestFloorFertile, tForestFloorFertile, pForestPalms, pForestPalms],
735 [stayFertileLand, avoidClasses(clForest, 15), new StaticConstraint([avoidClasses(clWater, 2), avoidCollisions])],
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");
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)]
749 [avoidCollisionsMines, avoidClasses(clRock, 10)],
751 scaleByMapSize(6, 24));
753 g_Map.log("Creating metal mines");
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)]
759 [avoidCollisionsMines, avoidClasses(clMetal, 10, clRock, 5)],
761 scaleByMapSize(6, 24));
763 g_Map.log("Creating berries");
764 createObjectGroupsByAreas(
765 new SimpleGroup([new SimpleObject(oBerryBushGrapes, 4, 6, 1, 2)], true, clFood),
768 scaleByMapSize(3, 15),
772 g_Map.log("Creating rhinos");
773 createObjectGroupsByAreas(
774 new SimpleGroup([new SimpleObject(oRhino, 1, 1, 0, 1)], true, clFood),
777 scaleByMapSize(2, 10),
781 g_Map.log("Creating warthogs");
782 createObjectGroupsByAreas(
783 new SimpleGroup([new SimpleObject(oWarthog, 1, 1, 0, 1)], true, clFood),
786 scaleByMapSize(2, 10),
790 g_Map.log("Creating giraffes");
792 new SimpleGroup([new SimpleObject(oGiraffe, 2, 3, 2, 4), new SimpleObject(oGiraffeInfant, 2, 3, 2, 4)], true, clFood),
795 scaleByMapSize(2, 10),
798 g_Map.log("Creating gazelles");
800 new SimpleGroup([new SimpleObject(oGazelle, 5, 7, 2, 4)], true, clFood),
803 scaleByMapSize(2, 10),
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),
813 [avoidCollisions, avoidClasses(clPlayer, 20)],
814 scaleByMapSize(2, 10),
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),
824 scaleByMapSize(2, 10),
828 g_Map.log("Creating crocodiles");
830 createObjectGroupsByAreas(
831 new SimpleGroup([new SimpleObject(oCrocodile, 2, 3, 3, 5)], true, clFood),
833 [nearWater, avoidCollisions],
834 scaleByMapSize(1, 6),
838 Engine.SetProgress(85);
840 g_Map.log("Marking irrigation canal tree area");
841 var areaIrrigationCanalTrees = createArea(
842 new MapBoundsPlacer(),
846 avoidClasses(clPassage, 3),
850 g_Map.log("Creating irrigation canal trees");
851 createObjectGroupsByAreas(
852 new SimpleGroup([new RandomObject(oPalms, 1, 1, 1, 1)], true, clForest),
854 avoidClasses(clForest, 1),
855 scaleByMapSize(100, 600),
857 [areaIrrigationCanalTrees]);
859 createStragglerTrees(
861 [stayFertileLand, avoidCollisions],
863 scaleByMapSize(50, 400),
866 createStragglerTrees(
868 [stayDesert, avoidCollisions],
870 scaleByMapSize(50, 400),
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),
877 new StaticConstraint([avoidClasses(clCliff, 1), new NearTileClassConstraint(clCliff, 5)]),
878 scaleByMapSize(1, 5),
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),
886 new StaticConstraint([
887 new HeightConstraint(heightHillArchers, heightHilltop),
888 avoidClasses(clCliff, 1, clSoldier, 1),
889 new NearTileClassConstraint(clCliff, 5)
891 scaleByMapSize(8, 100),
895 g_Map.log("Placing siege engines on the hilltop");
896 createObjectGroupsByAreas(
897 new SimpleGroup([new RandomObject(oPtolSiege, 1, 1, 1, 3)], true, clSoldier),
899 new StaticConstraint([new NearTileClassConstraint(clCliff, 5), avoidClasses(clCliff, 1, clSoldier, 1)]),
900 scaleByMapSize(1, 6),
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),
909 avoidCollisionsPyramids,
910 scaleByMapSize(3, 8),
914 g_Map.log("Placing treasures at the pyramid");
915 createObjectGroupsByAreas(
916 new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
918 avoidCollisionsPyramids,
919 scaleByMapSize(1, 10),
923 g_Map.log("Placing treasures on the hilltop");
924 createObjectGroupsByAreas(
925 new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
927 new StaticConstraint([avoidClasses(clCliff, 1, clTreasure, 1)]),
928 scaleByMapSize(3, 10),
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),
937 pathBorderConstraint,
938 scaleByMapSize(2, 40),
942 g_Map.log("Placing handcarts on the paths");
943 createObjectGroupsByAreas(
944 new SimpleGroup([new SimpleObject(aHandcart, 1, 1, 1, 1)], true),
946 pathBorderConstraint,
947 scaleByMapSize(0, 5),
951 g_Map.log("Placing fence in fertile land");
952 createObjectGroupsByAreas(
953 new SimpleGroup([new SimpleObject(aPlotFence, 1, 1, 1, 1)], true),
955 new StaticConstraint(avoidCollisions, avoidClasses(clWater, 4)),
956 scaleByMapSize(1, 10),
960 g_Map.log("Creating fish");
962 new SimpleGroup([new SimpleObject(oFish, 3, 4, 2, 3)], true, clFood),
964 [new StaticConstraint(stayClasses(clWater, 6)), avoidClasses(clFood, 12)],
965 scaleByMapSize(20, 120),
968 Engine.SetProgress(95);
970 avoidCollisions = new StaticConstraint(avoidCollisions);
973 aBushesDesert.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
974 aBushesDesert.map(bush => scaleByMapSize(20, 150) * randIntInclusive(1, 3)),
975 [stayDesert, avoidCollisions]);
978 aBushesFertileLand.map(bush => [new SimpleObject(bush, 0, 4, 2, 4)]),
979 aBushesFertileLand.map(bush => scaleByMapSize(20, 150) * randIntInclusive(1, 3)),
980 [stayFertileLand, avoidCollisions]);
983 [[new SimpleObject(aRock, 0, 4, 2, 4)]],
984 [[scaleByMapSize(80, 500)]],
985 [stayDesert, avoidCollisions]);
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),
996 new StaticConstraint(new NearTileClassConstraint(clFertileLand, 4)),
997 scaleByMapSize(50, 400),
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);
1023 setFogColor(0.69, 0.616, 0.541);
1026 setPPContrast(0.67);
1027 setPPSaturation(0.42);