2 * For historic reference, see
3 * http://www.jebelbarkal.org/images/maps/siteplan.jpg
6 Engine.LoadLibrary("rmgen");
7 Engine.LoadLibrary("rmgen-common");
8 Engine.LoadLibrary("heightmap");
10 function* GenerateMap(mapSettings)
12 TILE_CENTERED_HEIGHT_MAP = true;
14 const tSand = "desert_sand_dunes_100";
15 const tHilltop = ["new_savanna_dirt_c", "new_savanna_dirt_d"];
16 const tHillGround = ["savanna_dirt_rocks_a", "savanna_dirt_rocks_b", "savanna_dirt_rocks_c"];
17 const tHillCliff = ["savanna_cliff_a_red", "savanna_cliff_b_red"];
18 const tRoadDesert = "savanna_tile_a";
19 const tRoadFertileLand = "savanna_tile_a";
20 const tWater = "desert_sand_wet";
22 ["savanna_shrubs_a_wetseason", "alpine_grass_b_wild", "medit_shrubs_a", "steppe_grass_green_a"];
23 const tForestFloorFertile = pickRandom(tGrass);
24 const tGrassTransition1 = "desert_grass_a";
25 const tGrassTransition2 = "steppe_grass_dirt_66";
26 const tPath = "road2";
27 const tPathWild = "road_med";
29 const oAcacia = "gaia/tree/acacia";
30 const oPalmPath = "gaia/tree/cretan_date_palm_tall";
32 "gaia/tree/cretan_date_palm_tall",
33 "gaia/tree/cretan_date_palm_short",
34 "gaia/tree/palm_tropic",
35 "gaia/tree/date_palm",
36 "gaia/tree/senegal_date_palm",
37 "gaia/tree/medit_fan_palm"
39 const oBerryBushGrapes = "gaia/fruit/grapes";
40 const oBerryBushDesert = "gaia/fruit/berry_05";
41 const oStoneLargeDesert = "gaia/rock/desert_large";
42 const oStoneSmallDesert = "gaia/rock/desert_small";
43 const oMetalLargeDesert = "gaia/ore/desert_large";
44 const oMetalSmallDesert = "gaia/ore/desert_small";
45 const oStoneLargeFertileLand = "gaia/rock/desert_large";
46 const oStoneSmallFertileLand = "gaia/rock/greece_small";
47 const oMetalLargeFertileLand = "gaia/ore/desert_large";
48 const oMetalSmallFertileLand = "gaia/ore/temperate_small";
49 const oFoodTreasureBin = "gaia/treasure/food_bin";
50 const oFoodTreasureCrate = "gaia/treasure/food_crate";
51 const oFoodTreasureJars = "gaia/treasure/food_jars";
52 const oWoodTreasure = "gaia/treasure/wood";
53 const oStoneTreasure = "gaia/treasure/stone";
54 const oMetalTreasure = "gaia/treasure/metal";
55 const oTreasuresHill = [oWoodTreasure, oStoneTreasure, oMetalTreasure];
56 const oTreasuresCity =
57 [oFoodTreasureBin, oFoodTreasureCrate, oFoodTreasureJars].concat(oTreasuresHill);
58 const oGiraffe = "gaia/fauna_giraffe";
59 const oGiraffeInfant = "gaia/fauna_giraffe_infant";
60 const oGazelle = "gaia/fauna_gazelle";
61 const oRhino = "gaia/fauna_rhinoceros_white";
62 const oWarthog = "gaia/fauna_boar";
63 const oElephant = "gaia/fauna_elephant_african_bush";
64 const oElephantInfant = "gaia/fauna_elephant_african_infant";
65 const oLion = "gaia/fauna_lion";
66 const oLioness = "gaia/fauna_lioness";
67 const oCrocodile = "gaia/fauna_crocodile_nile";
68 const oFish = "gaia/fish/tilapia";
69 const oHawk = "birds/buzzard";
70 const oTempleApedemak = "structures/kush/temple";
71 const oTempleAmun = "structures/kush/temple_amun";
72 const oPyramidLarge = "structures/kush/pyramid_large";
73 const oPyramidSmall = "structures/kush/pyramid_small";
74 const oWonderPtol = "structures/ptol/wonder";
75 const oFortress = "structures/kush/fortress";
76 const oTower = mapSettings.Size >= 256 && getDifficulty() >= 3 ? "structures/kush/defense_tower" :
77 "structures/kush/sentry_tower";
78 const oHouse = "structures/kush/house";
79 const oMarket = "structures/kush/market";
80 const oForge = "structures/kush/forge";
81 const oBlemmyeCamp = "structures/kush/camp_blemmye";
82 const oNobaCamp = "structures/kush/camp_noba";
83 const oCivicCenter = "structures/kush/civil_centre";
84 const oBarracks = "structures/kush/barracks";
85 const oStable = "structures/kush/stable";
86 const oElephantStable = "structures/kush/elephant_stable";
87 const oWallMedium = "structures/kush/wall_medium";
88 const oWallGate = "structures/kush/wall_gate";
89 const oWallTower = "structures/kush/wall_tower";
90 const oPalisadeMedium = "structures/palisades_medium";
91 const oPalisadeGate = "structures/palisades_gate";
92 const oPalisadeTower = "structures/palisades_tower";
93 const oKushCitizenArcher = "units/kush/infantry_archer_b";
94 const oKushHealer = "units/kush/support_healer_b";
95 const oKushChampionArcher = "units/kush/champion_infantry_archer";
96 const oKushChampions = [
98 "units/kush/champion_infantry_amun",
99 "units/kush/champion_infantry_apedemak"
101 const oPtolSiege = ["units/ptol/siege_lithobolos_unpacked", "units/ptol/siege_polybolos_unpacked"];
102 const oTriggerPointCityPath = "trigger/trigger_point_A";
103 const oTriggerPointAttackerPatrol = "trigger/trigger_point_B";
105 const aPalmPath = actorTemplate("flora/trees/palm_cretan_date_tall");
106 const aRock = actorTemplate("geology/stone_savanna_med");
107 const aHandcart = actorTemplate("props/special/eyecandy/handcart_1");
108 const aPlotFence = actorTemplate("props/special/common/plot_fence");
109 const aStatueKush = actorTemplate("props/special/eyecandy/statues_kush");
111 "props/structures/kushites/statue_pedestal_rectangular",
112 "props/structures/kushites/statue_pedestal_rectangular_lion"
113 ].map(actorTemplate);
114 const aBushesFertileLand = [
115 ...new Array(3).fill("props/flora/shrub_spikes"),
116 ...new Array(3).fill("props/flora/ferns"),
117 "props/flora/shrub_tropic_plant_a",
118 "props/flora/shrub_tropic_plant_b",
119 "props/flora/shrub_tropic_plant_flower",
120 "props/flora/foliagebush",
122 "props/flora/bush_medit_la",
123 "props/flora/bush_medit_la_lush",
124 "props/flora/bush_medit_me_lush",
125 "props/flora/bush_medit_sm",
126 "props/flora/bush_medit_sm_lush",
127 "props/flora/bush_tempe_la_lush"
128 ].map(actorTemplate);
129 const aBushesCity = [
130 "props/flora/bush_dry_a",
131 "props/flora/bush_medit_la_dry",
132 "props/flora/bush_medit_me_dry",
133 "props/flora/bush_medit_sm",
134 "props/flora/bush_medit_sm_dry",
135 ].map(actorTemplate);
136 const aBushesDesert = [
137 "props/flora/bush_tempe_me_dry",
138 "props/flora/grass_soft_dry_large_tall",
139 "props/flora/grass_soft_dry_small_tall"
140 ].map(actorTemplate).concat(aBushesCity);
141 const aWaterDecoratives = ["props/flora/reeds_pond_lush_a"].map(actorTemplate);
143 const pForestPalms = [
145 ...oPalms.map(tree => tForestFloorFertile + TERRAIN_SEPARATOR + tree),
146 tForestFloorFertile];
148 const heightScale = num => num * mapSettings.Size / 320;
150 const minHeightSource = 3;
151 const maxHeightSource = 800;
153 globalThis.g_Map = new RandomMap(0, tSand);
154 const mapSize = g_Map.getSize();
155 const mapCenter = g_Map.getCenter();
156 const mapBounds = g_Map.getBounds();
157 const numPlayers = getNumPlayers();
159 const clHill = g_Map.createTileClass();
160 const clCliff = g_Map.createTileClass();
161 const clDesert = g_Map.createTileClass();
162 const clFertileLand = g_Map.createTileClass();
163 const clWater = g_Map.createTileClass();
164 const clIrrigationCanal = g_Map.createTileClass();
165 const clPassage = g_Map.createTileClass();
166 const clPlayer = g_Map.createTileClass();
167 const clBaseResource = g_Map.createTileClass();
168 const clFood = g_Map.createTileClass();
169 const clForest = g_Map.createTileClass();
170 const clRock = g_Map.createTileClass();
171 const clMetal = g_Map.createTileClass();
172 const clTreasure = g_Map.createTileClass();
173 const clCity = g_Map.createTileClass();
174 const clPath = g_Map.createTileClass();
175 const clPathStatues = g_Map.createTileClass();
176 const clPathCrossing = g_Map.createTileClass();
177 const clStatue = g_Map.createTileClass();
178 const clWall = g_Map.createTileClass();
179 const clGate = g_Map.createTileClass();
180 const clRoad = g_Map.createTileClass();
181 const clTriggerPointCityPath = g_Map.createTileClass();
182 const clTriggerPointMap = g_Map.createTileClass();
183 const clSoldier = g_Map.createTileClass();
184 const clTower = g_Map.createTileClass();
185 const clFortress = g_Map.createTileClass();
186 const clTemple = g_Map.createTileClass();
187 const clRitualPlace = g_Map.createTileClass();
188 const clPyramid = g_Map.createTileClass();
189 const clHouse = g_Map.createTileClass();
190 const clForge = g_Map.createTileClass();
191 const clStable = g_Map.createTileClass();
192 const clElephantStable = g_Map.createTileClass();
193 const clCivicCenter = g_Map.createTileClass();
194 const clBarracks = g_Map.createTileClass();
195 const clBlemmyeCamp = g_Map.createTileClass();
196 const clNobaCamp = g_Map.createTileClass();
197 const clMarket = g_Map.createTileClass();
198 const clDecorative = g_Map.createTileClass();
200 const riverAngle = 0.05 * Math.PI;
202 const hillRadius = scaleByMapSize(40, 120);
203 const positionPyramids = new Vector2D(fractionToTiles(0.15), fractionToTiles(0.75));
206 const pathWidthCenter = 10;
207 const pathWidthSecondary = 6;
209 const placeNapataWall = mapSize < 192 || getDifficulty() < 2 ? false : getDifficulty() < 3 ?
210 "napata_palisade" : "napata_wall";
212 const layoutFertileLandTextures = [
214 "left": fractionToTiles(0),
215 "right": fractionToTiles(0.04),
216 "terrain": createTerrain(tGrassTransition1),
217 "tileClass": clFertileLand
220 "left": fractionToTiles(0.04),
221 "right": fractionToTiles(0.08),
222 "terrain": createTerrain(tGrassTransition2),
223 "tileClass": clDesert
227 const layoutKushTemples = [
228 ...new Array(2).fill(0).map((v, i) =>
230 "template": oTempleApedemak,
231 "pathOffset": new Vector2D(0, 9),
232 "minMapSize": i == 0 ? 320 : 0
235 "template": oTempleAmun,
236 "pathOffset": new Vector2D(0, 12),
240 "template": oWonderPtol,
241 "pathOffset": new Vector2D(0, scaleByMapSize(9, 14)),
245 "template": oTempleAmun,
246 "pathOffset": new Vector2D(0, 12),
249 ...new Array(2).fill(0).map((v, i) =>
251 "template": oTempleApedemak,
252 "pathOffset": new Vector2D(0, 9),
253 "minMapSize": i == 0 ? 320 : 0
255 ].filter(temple => mapSize >= temple.minMapSize);
258 * The buildings are set as uncapturable, otherwise the player would gain
259 * the buildings via root territory and can delete them without effort.
260 * Keep the entire city uncapturable as a consistent property of the city.
262 const layoutKushCity = [
264 "templateName": "uncapturable|" + oHouse,
265 "difficulty": "Very Easy",
266 "painters": new TileClassPainter(clHouse)
269 "templateName": oFortress,
270 "difficulty": "Medium",
271 "constraints": [avoidClasses(clFortress, 25), new NearTileClassConstraint(clPath, 8)],
272 "painters": new TileClassPainter(clFortress)
275 "templateName": oCivicCenter,
276 "difficulty": "Easy",
277 "constraints": [avoidClasses(clCivicCenter, 60), new NearTileClassConstraint(clPath, 8)],
278 "painters": new TileClassPainter(clCivicCenter)
281 "templateName": oElephantStable,
282 "difficulty": "Easy",
283 "constraints": avoidClasses(clElephantStable, 10),
284 "painters": new TileClassPainter(clElephantStable)
287 "templateName": oStable,
288 "difficulty": "Easy",
289 "constraints": avoidClasses(clStable, 20),
290 "painters": new TileClassPainter(clStable)
293 "templateName": oBarracks,
294 "difficulty": "Easy",
295 "constraints": avoidClasses(clBarracks, 12),
296 "painters": new TileClassPainter(clBarracks)
299 "templateName": oTower,
300 "difficulty": "Easy",
301 "constraints": avoidClasses(clTower, 17),
302 "painters": new TileClassPainter(clTower)
305 "templateName": "uncapturable|" + oMarket,
306 "difficulty": "Very Easy",
307 "constraints": avoidClasses(clMarket, 15),
308 "painters": new TileClassPainter(clMarket)
311 "templateName": "uncapturable|" + oForge,
312 "difficulty": "Very Easy",
313 "constraints": avoidClasses(clForge, 30),
314 "painters": new TileClassPainter(clForge)
317 "templateName": oNobaCamp,
318 "difficulty": "Easy",
319 "constraints": avoidClasses(clNobaCamp, 30),
320 "painters": new TileClassPainter(clNobaCamp)
323 "templateName": oBlemmyeCamp,
324 "difficulty": "Easy",
325 "constraints": avoidClasses(clBlemmyeCamp, 30),
326 "painters": new TileClassPainter(clBlemmyeCamp)
328 ].filter(building => getDifficulty() >=
329 getDifficulties().find(difficulty => difficulty.Name == building.difficulty).Difficulty);
331 g_WallStyles.napata_wall = {
332 "short": readyWallElement("uncapturable|" + oWallMedium),
333 "medium": readyWallElement("uncapturable|" + oWallMedium),
334 "tower": readyWallElement("uncapturable|" + oWallTower),
335 "gate": readyWallElement("uncapturable|" + oWallGate),
339 g_WallStyles.napata_palisade = {
340 "short": readyWallElement("uncapturable|" + oPalisadeMedium),
341 "medium": readyWallElement("uncapturable|" + oPalisadeMedium),
342 "tower": readyWallElement("uncapturable|" + oPalisadeTower),
343 "gate": readyWallElement("uncapturable|" + oPalisadeGate),
349 g_Map.log("Loading hill heightmap");
351 new MapBoundsPlacer(),
352 new HeightmapPainter(
354 new Vector2D(-12, scaleByMapSize(-12, -25)),
356 convertHeightmap1Dto2D(
357 Engine.LoadMapTerrain("maps/random/jebel_barkal.pmp").height)),
361 const heightDesert = g_Map.getHeight(mapCenter);
362 const heightFertileLand = heightDesert - heightScale(2);
363 const heightShoreline = heightFertileLand - heightScale(0.5);
364 const heightWaterLevel = heightFertileLand - heightScale(3);
365 const heightPassage = heightWaterLevel - heightScale(1.5);
366 const heightIrrigationCanal = heightWaterLevel - heightScale(4);
367 const heightSeaGround = heightWaterLevel - heightScale(8);
368 const heightHill = heightDesert + heightScale(4);
369 const heightHilltop = heightHill + heightScale(90);
370 const heightHillArchers = (heightHilltop + heightHill) / 2;
371 const heightOffsetPath = heightScale(-2.5);
372 const heightOffsetRoad = heightScale(-1.5);
373 const heightOffsetWalls = heightScale(2.5);
374 const heightOffsetStatue = heightScale(2.5);
376 g_Map.log("Flattening land");
378 new MapBoundsPlacer(),
379 new ElevationPainter(heightDesert),
380 new HeightConstraint(-Infinity, heightDesert));
383 const widthFertileLand = fractionToTiles(0.33);
386 "start": new Vector2D(mapBounds.left, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
387 "end": new Vector2D(mapBounds.right, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
388 "width": 2 * widthFertileLand,
391 "heightLand": heightDesert,
392 "heightRiverbed": heightFertileLand,
395 "waterFunc": (position, height, riverFraction) => {
396 createTerrain(tGrass).place(position);
397 clFertileLand.add(position);
399 "landFunc": (position, shoreDist1, shoreDist2) => {
401 for (const riv of layoutFertileLandTextures)
402 if (riv.left < +shoreDist1 && +shoreDist1 < riv.right ||
403 riv.left < -shoreDist2 && -shoreDist2 < riv.right)
405 riv.tileClass.add(position);
406 riv.terrain.place(position);
414 "start": new Vector2D(mapBounds.left, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
415 "end": new Vector2D(mapBounds.right, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
416 "width": fractionToTiles(0.2),
419 "heightLand": heightFertileLand,
420 "heightRiverbed": heightSeaGround,
426 g_Map.log("Computing player locations");
427 const playerIDs = sortAllPlayers();
428 const playerPosition = playerPlacementArcs(
431 fractionToTiles(0.38),
432 riverAngle - 0.5 * Math.PI,
438 g_Map.log("Marking player positions");
439 for (const position of playerPosition)
440 addCivicCenterAreaToClass(position, clPlayer);
443 g_Map.log("Marking water");
445 new MapBoundsPlacer(),
447 new TileClassPainter(clWater),
448 new TileClassUnPainter(clFertileLand)
450 new HeightConstraint(-Infinity, heightWaterLevel));
452 g_Map.log("Marking desert");
453 const avoidWater = new StaticConstraint(avoidClasses(clWater, 0));
455 new MapBoundsPlacer(),
456 new TileClassPainter(clDesert),
458 new HeightConstraint(-Infinity, heightHill),
460 avoidClasses(clFertileLand, 0)
463 const stayDesert = new StaticConstraint(stayClasses(clDesert, 0));
464 const stayFertileLand = new StaticConstraint(stayClasses(clFertileLand, 0));
466 g_Map.log("Finding possible irrigation canal locations");
467 const irrigationCanalAreas = [];
468 for (let i = 0; i < 30; ++i)
470 const x = fractionToTiles(randFloat(0, 1));
471 irrigationCanalAreas.push(
474 new Vector2D(x, mapBounds.bottom).rotateAround(-riverAngle, mapCenter),
475 new Vector2D(x, mapBounds.top).rotateAround(-riverAngle, mapCenter),
483 avoidClasses(clDesert, 2)));
486 g_Map.log("Creating irrigation canals");
487 const irrigationCanalLocations = [];
488 for (const area of irrigationCanalAreas)
490 if (!area.getPoints().length ||
491 area.getPoints().some(point => !avoidClasses(
492 clPlayer, scaleByMapSize(8, 13),
493 clIrrigationCanal, scaleByMapSize(15, 25)).allows(point)))
496 irrigationCanalLocations.push(
497 pickRandom(area.getPoints()).clone().rotateAround(riverAngle, mapCenter).x);
499 new MapBoundsPlacer(),
501 new SmoothElevationPainter(ELEVATION_SET, heightIrrigationCanal, 1),
502 new TileClassPainter(clIrrigationCanal)
505 new StayAreasConstraint([area]),
506 new HeightConstraint(heightIrrigationCanal, heightDesert)
510 g_Map.log("Creating passages");
511 let previousPassageY = randIntInclusive(0, widthFertileLand);
512 const areasPassages = [];
513 irrigationCanalLocations.sort((a, b) => a - b);
514 for (let i = 0; i < irrigationCanalLocations.length; ++i)
516 const previous = i == 0 ? mapBounds.left : irrigationCanalLocations[i - 1];
517 const next = i == irrigationCanalLocations.length - 1 ? mapBounds.right :
518 irrigationCanalLocations[i + 1];
520 const x1 = (irrigationCanalLocations[i] + previous) / 2;
521 const x2 = (irrigationCanalLocations[i] + next) / 2;
524 // The passages should be at different locations, so that enemies
525 // can't attack each other easily
526 for (let tries = 0; tries < 100; ++tries)
528 y = (previousPassageY +
529 randIntInclusive(0.2 * widthFertileLand, 0.8 * widthFertileLand)) %
532 const pos = new Vector2D((x1 + x2) / 2, y).rotateAround(-riverAngle, mapCenter).round();
534 if (g_Map.validTilePassable(new Vector2D(pos.x, pos.y)) &&
535 avoidClasses(clDesert, 12).allows(pos) &&
536 new HeightConstraint(heightIrrigationCanal, heightFertileLand).allows(pos))
543 new Vector2D(x1, y).rotateAround(-riverAngle, mapCenter),
544 new Vector2D(x2, y).rotateAround(-riverAngle, mapCenter),
552 new ElevationPainter(heightPassage),
553 new TileClassPainter(clPassage)
556 new HeightConstraint(-Infinity, heightPassage),
557 stayClasses(clFertileLand, 2)
560 if (!area || !area.getPoints().length)
563 previousPassageY = y;
564 areasPassages.push(area);
568 g_Map.log("Marking hill");
570 new MapBoundsPlacer(),
571 new TileClassPainter(clHill),
572 new HeightConstraint(heightHill, Infinity));
574 g_Map.log("Marking water");
575 const areaWater = createArea(
576 new MapBoundsPlacer(),
577 new TileClassPainter(clWater),
578 new HeightConstraint(-Infinity, heightWaterLevel));
580 g_Map.log("Painting water and shoreline");
582 new MapBoundsPlacer(),
583 new TerrainPainter(tWater),
584 new HeightConstraint(-Infinity, heightShoreline));
586 g_Map.log("Painting hill");
587 const areaHill = createArea(
588 new MapBoundsPlacer(),
589 new TerrainPainter(tHillGround),
590 new HeightConstraint(heightHill, Infinity));
592 g_Map.log("Painting hilltop");
593 const areaHilltop = createArea(
594 new MapBoundsPlacer(),
595 new TerrainPainter(tHilltop),
597 new HeightConstraint(heightHilltop, Infinity),
598 new SlopeConstraint(-Infinity, 2)
603 for (let i = 0; i < numPlayers; ++i)
605 const isDesert = clDesert.has(playerPosition[i]);
607 "playerID": playerIDs[i],
608 "playerPosition": playerPosition[i],
609 "PlayerTileClass": clPlayer,
610 "BaseResourceClass": clBaseResource,
611 "baseResourceConstraint": avoidClasses(clPlayer, 4, clWater, 4),
612 "Walls": mapSize <= 256 || getDifficulty() >= 3 ? "towers" : "walls",
614 "outerTerrain": isDesert ? tRoadDesert : tRoadFertileLand,
615 "innerTerrain": isDesert ? tRoadDesert : tRoadFertileLand
618 "template": oGazelle,
620 "minGroupDistance": 2,
621 "maxGroupDistance": 4,
626 "template": isDesert ? oBerryBushDesert : oBerryBushGrapes
630 { "template": isDesert ? oMetalLargeDesert : oMetalLargeFertileLand },
631 { "template": isDesert ? oStoneLargeDesert : oStoneLargeFertileLand }
635 "template": isDesert ? oAcacia : pickRandom(oPalms),
636 "count": isDesert ? scaleByMapSize(5, 10) : scaleByMapSize(15, 30)
642 "template": oWoodTreasure,
643 "count": isDesert ? 4 : 0
646 "template": oStoneTreasure,
647 "count": isDesert ? 1 : 0
650 "template": oMetalTreasure,
651 "count": isDesert ? 1 : 0
656 "template": isDesert ? aRock : pickRandom(aBushesFertileLand)
661 g_Map.log("Placing pyramids");
662 const areaPyramids = createArea(new DiskPlacer(scaleByMapSize(5, 14), positionPyramids));
663 // Retry loops are needed due to the self-avoidance
664 createObjectGroupsByAreas(
667 [oPyramidLarge, oPyramidSmall],
668 scaleByMapSize(1, 6),
669 scaleByMapSize(2, 8),
670 scaleByMapSize(6, 8),
671 scaleByMapSize(6, 14),
674 scaleByMapSize(6, 8))],
685 // The city is a circle segment of this maximum size
686 g_Map.log("Computing city grid");
687 const gridCenter = new Vector2D(0, fractionToTiles(0.3)).rotate(-riverAngle).add(mapCenter).round();
688 const gridMaxAngle = Math.min(scaleByMapSize(1/3, 1), 2/3) * Math.PI;
689 const gridStartAngle = -Math.PI / 2 -gridMaxAngle / 2 + riverAngle;
690 const gridRadius = y => hillRadius + 18 * y;
692 const gridPointsX = layoutKushTemples.length;
693 const gridPointsY = Math.floor(scaleByMapSize(2, 5));
694 const gridPointXCenter = Math.floor(gridPointsX / 2);
695 const gridPointYCenter = Math.floor(gridPointsY / 2);
697 // Maps from grid position to map position
698 const cityGridPosition = [];
699 const cityGridAngle = [];
700 for (let y = 0; y < gridPointsY; ++y)
701 [cityGridPosition[y], cityGridAngle[y]] = distributePointsOnCircularSegment(
702 gridPointsX, gridMaxAngle, gridStartAngle, gridRadius(y), gridCenter);
704 g_Map.log("Marking city path crossings");
705 for (const y in cityGridPosition)
706 for (const x in cityGridPosition[y])
708 cityGridPosition[y][x].round();
710 new DiskPlacer(pathWidth, cityGridPosition[y][x]),
712 new TileClassPainter(clPath),
713 new TileClassPainter(clPathCrossing)
717 g_Map.log("Marking horizontal city paths");
718 const areasCityPaths = [];
719 for (let y = 0; y < gridPointsY; ++y)
720 for (let x = 1; x < gridPointsX; ++x)
722 const width = y == gridPointYCenter ? pathWidthSecondary : pathWidth;
725 new PathPlacer(cityGridPosition[y][x - 1], cityGridPosition[y][x], width, 0,
727 new TileClassPainter(clPath)));
730 g_Map.log("Marking vertical city paths");
731 for (let y = 1; y < gridPointsY; ++y)
732 for (let x = 0; x < gridPointsX; ++x)
735 Math.abs(x - gridPointXCenter) == 0 ? pathWidthCenter :
736 Math.abs(x - gridPointXCenter) == 1 ? pathWidthSecondary : pathWidth;
740 new PathPlacer(cityGridPosition[y - 1][x], cityGridPosition[y][x], width, 0,
742 new TileClassPainter(clPath)));
746 g_Map.log("Placing kushite temples");
747 const entitiesTemples = [];
748 const templePosition = [];
749 for (let i = 0; i < layoutKushTemples.length; ++i)
751 const x = i + (gridPointsX - layoutKushTemples.length) / 2;
752 templePosition[i] = Vector2D.add(cityGridPosition[0][x],
753 layoutKushTemples[i].pathOffset.rotate(-Math.PI / 2 - cityGridAngle[0][x]));
754 entitiesTemples[i] = g_Map.placeEntityPassable(layoutKushTemples[i].template, 0,
755 templePosition[i], cityGridAngle[0][x]);
758 g_Map.log("Marking temple area");
760 new EntitiesObstructionPlacer(entitiesTemples, 0, Infinity),
761 new TileClassPainter(clTemple));
763 g_Map.log("Smoothing temple ground");
765 new MapBoundsPlacer(),
766 new ElevationBlendingPainter(heightDesert, 0.8),
767 new NearTileClassConstraint(clTemple, 0));
769 g_Map.log("Painting cliffs");
771 new MapBoundsPlacer(),
773 new TerrainPainter(tHillCliff),
774 new TileClassPainter(clCliff)
777 stayClasses(clHill, 0),
778 new SlopeConstraint(2, Infinity)
781 g_Map.log("Painting temple ground");
783 new MapBoundsPlacer(),
784 new TerrainPainter(tPathWild),
786 new NearTileClassConstraint(clTemple, 1),
787 avoidClasses(clPath, 0, clCliff, 1)
790 g_Map.log("Placing lion statues in the central path");
791 const statueCount = scaleByMapSize(10, 40);
792 const centralPathStart = cityGridPosition[0][gridPointXCenter];
793 const centralPathLength = centralPathStart.distanceTo(
794 cityGridPosition[gridPointsY - 1][gridPointXCenter]);
795 const centralPathAngle = cityGridAngle[0][gridPointXCenter];
796 for (let i = 0; i < 2; ++i)
797 for (let stat = 0; stat < statueCount; ++stat)
799 const start = new Vector2D(0, pathWidthCenter * 3/4 * (i - 0.5))
800 .rotate(centralPathAngle)
801 .add(centralPathStart);
802 const position = new Vector2D(centralPathLength, 0)
803 .mult(stat / statueCount)
804 .rotate(-centralPathAngle)
806 .add(new Vector2D(0.5, 0.5));
808 if (!avoidClasses(clPathCrossing, 2).allows(position))
811 g_Map.placeEntityPassable(pickRandom(aStatues), 0, position,
812 centralPathAngle - Math.PI * (i + 0.5));
813 clPathStatues.add(position.round());
816 g_Map.log("Placing guardian infantry in the central path");
817 const centralChampionsCount = scaleByMapSize(2, 40);
818 for (let i = 0; i < 2; ++i)
819 for (let champ = 0; champ < centralChampionsCount; ++champ)
821 const start = new Vector2D(0, pathWidthCenter * 1/2 * (i - 0.5))
822 .rotate(-centralPathAngle)
823 .add(centralPathStart);
824 const position = new Vector2D(centralPathLength, 0)
825 .mult(champ / centralChampionsCount)
826 .rotate(-centralPathAngle)
828 .add(new Vector2D(0.5, 0.5));
830 if (!avoidClasses(clPathCrossing, 2).allows(position))
833 g_Map.placeEntityPassable(pickRandom(oKushChampions), 0, position,
834 centralPathAngle - Math.PI * (i - 0.5));
835 clPathStatues.add(position.round());
838 g_Map.log("Placing kushite statues in the secondary paths");
839 for (const x of [gridPointXCenter - 1, gridPointXCenter + 1])
841 g_Map.placeEntityAnywhere(aStatueKush, 0, cityGridPosition[gridPointYCenter][x],
842 cityGridAngle[gridPointYCenter][x]);
843 clPathStatues.add(cityGridPosition[gridPointYCenter][x]);
846 g_Map.log("Creating ritual place near the wonder");
847 const ritualPosition = Vector2D.average([
848 templePosition[Math.floor(templePosition.length / 2) - 1],
849 templePosition[Math.ceil(templePosition.length / 2) - 1],
850 cityGridPosition[0][gridPointXCenter],
851 cityGridPosition[0][gridPointXCenter - 1]
855 (cityGridAngle[0][gridPointXCenter] + cityGridAngle[0][gridPointXCenter - 1]) / 2 + Math.PI / 2;
857 g_Map.placeEntityPassable(aStatueKush, 0, ritualPosition, ritualAngle - Math.PI / 2);
860 new DiskPlacer(scaleByMapSize(4, 6), ritualPosition),
862 new LayeredPainter([tPathWild, tPath], [1]),
863 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetPath, 2),
864 new TileClassPainter(clRitualPlace)
866 avoidClasses(clCliff, 1));
869 new DiskPlacer(0, new Vector2D(-1, -1).add(ritualPosition)),
870 new ElevationPainter(heightDesert + heightOffsetStatue));
872 g_Map.log("Placing healers at the ritual place");
873 const [healerPosition, healerAngle] = distributePointsOnCircularSegment(
874 scaleByMapSize(2, 10), Math.PI, ritualAngle, scaleByMapSize(2, 3), ritualPosition);
875 for (let i = 0; i < healerPosition.length; ++i)
876 g_Map.placeEntityPassable(oKushHealer, 0, healerPosition[i], healerAngle[i] + Math.PI);
878 g_Map.log("Placing statues at the ritual place");
879 const [statuePosition, statueAngle] = distributePointsOnCircularSegment(
880 scaleByMapSize(4, 8), Math.PI, ritualAngle, scaleByMapSize(3, 4), ritualPosition);
881 for (let i = 0; i < statuePosition.length; ++i)
882 g_Map.placeEntityPassable(pickRandom(aStatues), 0, statuePosition[i], statueAngle[i] + Math.PI);
884 g_Map.log("Placing palms at the ritual place");
885 const palmPosition = distributePointsOnCircularSegment(
886 scaleByMapSize(6, 16), Math.PI, ritualAngle, scaleByMapSize(4, 5), ritualPosition)[0];
887 for (let i = 0; i < palmPosition.length; ++i)
888 if (avoidClasses(clTemple, 1).allows(palmPosition[i]))
889 g_Map.placeEntityPassable(oPalmPath, 0, palmPosition[i], randomAngle());
891 g_Map.log("Painting city paths");
892 const areaPaths = createArea(
893 new MapBoundsPlacer(),
895 new LayeredPainter([tPathWild, tPath], [1]),
896 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetPath, 1)
898 stayClasses(clPath, 0));
900 g_Map.log("Placing triggerpoints on city paths");
901 createObjectGroupsByAreas(
902 new SimpleGroup([new SimpleObject(oTriggerPointCityPath, 1, 1, 0, 0)], true,
903 clTriggerPointCityPath),
905 [avoidClasses(clTriggerPointCityPath, 8), stayClasses(clPathCrossing, 2)],
906 scaleByMapSize(20, 100),
910 g_Map.log("Placing city districts");
911 for (let y = 1; y < gridPointsY; ++y)
912 for (let x = 1; x < gridPointsX; ++x)
914 new ConvexPolygonPlacer(
916 cityGridPosition[y - 1][x - 1],
917 cityGridPosition[y - 1][x],
918 cityGridPosition[y][x - 1],
919 cityGridPosition[y][x]
922 new TerrainPainter(tRoadDesert),
923 new CityPainter(layoutKushCity,
924 (-cityGridAngle[y][x - 1] - cityGridAngle[y][x]) / 2, 0),
925 new TileClassPainter(clCity)
927 new StaticConstraint(avoidClasses(clPath, 0)));
932 g_Map.log("Placing front walls");
933 const wallGridMaxAngleSummand = scaleByMapSize(0.04, 0.03) * Math.PI;
934 const wallGridStartAngle = gridStartAngle - wallGridMaxAngleSummand / 2;
935 const wallGridRadiusFront = gridRadius(gridPointsY - 1) + pathWidth - 1;
936 const wallGridMaxAngleFront = gridMaxAngle + wallGridMaxAngleSummand;
937 let entitiesWalls = placeCircularWall(
940 ["tower", "short", "tower", "gate", "tower", "medium", "tower", "short"],
944 wallGridMaxAngleFront,
949 g_Map.log("Placing side and back walls");
950 const wallGridRadiusBack = hillRadius - scaleByMapSize(15, 25);
951 const wallGridMaxAngleBack = gridMaxAngle + wallGridMaxAngleSummand;
952 const wallGridPositionFront =
953 distributePointsOnCircularSegment(gridPointsX, wallGridMaxAngleBack, wallGridStartAngle,
954 wallGridRadiusFront, gridCenter)[0];
955 const wallGridPositionBack =
956 distributePointsOnCircularSegment(gridPointsX, wallGridMaxAngleBack, wallGridStartAngle,
957 wallGridRadiusBack, gridCenter)[0];
958 const wallGridPosition =
960 wallGridPositionFront[0],
961 ...wallGridPositionBack,
962 wallGridPositionFront[wallGridPositionFront.length - 1]
964 for (let x = 1; x < wallGridPosition.length; ++x)
965 entitiesWalls = entitiesWalls.concat(
967 wallGridPosition[x - 1],
969 ["tower", "gate", "tower", "short", "tower", "short", "tower"],
973 avoidClasses(clHill, 0, clTemple, 0)));
975 g_Map.log("Marking walls");
977 new EntitiesObstructionPlacer(entitiesWalls, 0, Infinity),
978 new TileClassPainter(clWall));
980 g_Map.log("Marking gates");
981 entitiesGates = entitiesWalls.filter(entity => entity.templateName.endsWith(oWallGate));
983 new EntitiesObstructionPlacer(entitiesGates, 0, Infinity),
984 new TileClassPainter(clGate));
986 g_Map.log("Painting wall terrain");
988 new MapBoundsPlacer(),
990 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetWalls, 2),
991 new TerrainPainter(tPathWild)
994 new NearTileClassConstraint(clWall, 1),
995 avoidClasses(clCliff, 0)
998 g_Map.log("Painting gate terrain");
999 for (const entity of entitiesGates)
1001 new DiskPlacer(pathWidth, entity.GetPosition2D()),
1003 new LayeredPainter([tPathWild, tPath], [1]),
1004 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetPath, 2),
1007 avoidClasses(clCliff, 0, clPath, 0, clCity, 0),
1008 new NearTileClassConstraint(clPath, pathWidth + 1)
1013 g_Map.log("Finding road starting points");
1014 const roadStartLocations = shuffleArray(
1016 entitiesGates.map(entity => entity.GetPosition2D()) :
1018 ...cityGridPosition.map(gridPos => gridPos[0]),
1019 ...cityGridPosition.map(gridPos => gridPos[gridPos.length - 1]),
1020 ...cityGridPosition[cityGridPosition.length - 1]
1023 g_Map.log("Finding possible roads");
1024 let roadConstraint = new StaticConstraint(
1027 avoidClasses(clHill, 0, clCity, 0, clPyramid, 6, clPlayer, 16)
1030 const areaCityPaths =
1031 new Area(areasCityPaths.reduce((points, area) => points.concat(area.getPoints()), []));
1032 const areaRoads = [];
1033 for (const roadStart of roadStartLocations)
1035 if (areaRoads.length >= scaleByMapSize(2, 5))
1038 const closestPoint = areaCityPaths.getClosestPointTo(roadStart);
1039 roadConstraint = new StaticConstraint([roadConstraint, avoidClasses(clRoad, 20)]);
1040 for (let tries = 0; tries < 30; ++tries)
1042 const area = createArea(
1044 Vector2D.add(closestPoint,
1045 new Vector2D(0, 3/4 * mapSize).rotate(closestPoint.angleTo(roadStart))),
1047 scaleByMapSize(5, 3),
1053 new TileClassPainter(clRoad),
1056 if (area && area.getPoints().length)
1058 areaRoads.push(area);
1064 g_Map.log("Painting roads");
1066 new MapBoundsPlacer(),
1068 new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetRoad, 1),
1069 new LayeredPainter([tPathWild, tPath], [1]),
1071 [stayClasses(clRoad, 0), avoidClasses(clPath, 0)]);
1073 g_Map.log("Marking road palm area");
1074 const areaRoadPalms = createArea(
1075 new MapBoundsPlacer(),
1078 new NearTileClassConstraint(clRoad, 1),
1079 avoidClasses(clRoad, 0, clPath, 1, clWall, 4, clGate, 4)
1082 if (areaRoadPalms && areaRoadPalms.getPoints().length)
1084 g_Map.log("Placing road palms");
1085 createObjectGroupsByAreas(
1086 new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
1088 avoidClasses(clForest, 2, clGate, 7),
1089 scaleByMapSize(40, 250),
1093 g_Map.log("Placing road bushes");
1094 createObjectGroupsByAreas(
1095 new SimpleGroup([new RandomObject(aBushesCity, 1, 1, 0, 0)], true, clForest),
1097 avoidClasses(clForest, 1),
1098 scaleByMapSize(40, 200),
1104 g_Map.log("Marking city bush area");
1105 const areaCityBushes =
1107 new MapBoundsPlacer(),
1110 new NearTileClassConstraint(clPath, 1),
1122 clElephantStable, 1,
1131 g_Map.log("Marking city palm area");
1132 const areaCityPalms =
1134 new MapBoundsPlacer(),
1137 new StayAreasConstraint([areaCityBushes]),
1138 avoidClasses(clElephantStable, 3)
1141 g_Map.log("Placing city palms");
1142 createObjectGroupsByAreas(
1143 new SimpleGroup([new SimpleObject(aPalmPath, 1, 1, 0, 0)], true, clForest),
1145 avoidClasses(clForest, 3),
1146 scaleByMapSize(40, 400),
1150 g_Map.log("Placing city bushes");
1151 createObjectGroupsByAreas(
1152 new SimpleGroup([new RandomObject(aBushesCity, 1, 1, 0, 0)], true, clForest),
1154 avoidClasses(clForest, 1),
1155 scaleByMapSize(20, 200),
1159 if (placeNapataWall)
1161 g_Map.log("Marking wall palm area");
1162 const areaWallPalms = createArea(
1163 new MapBoundsPlacer(),
1165 new StaticConstraint([
1166 new NearTileClassConstraint(clWall, 2),
1167 avoidClasses(clPath, 1, clRoad, 1, clWall, 1, clGate, 3, clTemple, 2, clHill, 6)
1170 g_Map.log("Placing wall palms");
1171 createObjectGroupsByAreas(
1172 new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
1174 avoidClasses(clForest, 2),
1175 scaleByMapSize(40, 250),
1181 new StaticConstraint(avoidClasses(
1192 scaleByMapSize(30, 300),
1200 g_Map.log("Setting up common constraints and areas");
1201 const nearWater = new NearTileClassConstraint(clWater, 3);
1202 const avoidCollisionsNomad = new AndConstraint(
1204 new StaticConstraint(avoidClasses(
1205 clCliff, 0, clHill, 0, clPlayer, 15, clWater, 1, clPath, 2, clRitualPlace, 10,
1206 clTemple, 4, clPyramid, 7, clCity, 4, clWall, 4, clGate, 8)),
1207 avoidClasses(clForest, 1, clRock, 4, clMetal, 4, clFood, 2, clSoldier, 1, clTreasure, 1)
1210 let avoidCollisions = new AndConstraint(
1212 avoidCollisionsNomad,
1213 new StaticConstraint(avoidClasses(clRoad, 6, clFood, 6))
1216 const areaDesert = createArea(new MapBoundsPlacer(), undefined, stayDesert);
1217 const areaFertileLand = createArea(new MapBoundsPlacer(), undefined, stayFertileLand);
1220 [tForestFloorFertile, tForestFloorFertile, tForestFloorFertile, pForestPalms, pForestPalms],
1223 avoidClasses(clForest, 15),
1224 new StaticConstraint([avoidClasses(clWater, 2), avoidCollisions])
1227 scaleByMapSize(250, 2000));
1229 g_Map.log("Creating mines");
1230 const avoidCollisionsMines = [
1231 avoidClasses(clRock, 10, clMetal, 10),
1232 new StaticConstraint(avoidClasses(
1233 clWater, 4, clCliff, 4, clCity, 4, clRitualPlace, 10, clPlayer, 20, clForest, 4,
1234 clPyramid, 6, clTemple, 4, clPath, 4, clRoad, 4, clGate, 8))
1237 const mineObjects = (templateSmall, templateLarge) => ({
1239 new SimpleObject(templateSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1),
1240 new SimpleObject(templateLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)
1243 new SimpleObject(templateSmall, 3, 4, 1, 3, 0, 2 * Math.PI, 1)
1247 const mineObjectsPerBiome = [
1249 "desert": mineObjects(oMetalSmallDesert, oMetalLargeDesert),
1250 "fertileLand": mineObjects(oMetalSmallFertileLand, oMetalLargeFertileLand),
1251 "tileClass": clMetal
1254 "desert": mineObjects(oStoneSmallDesert, oStoneLargeDesert),
1255 "fertileLand": mineObjects(oStoneSmallFertileLand, oStoneLargeFertileLand),
1260 for (let i = 0; i < scaleByMapSize(6, 22); ++i)
1262 const mineObjectsBiome = pickRandom(mineObjectsPerBiome);
1263 for (const k in mineObjectsBiome.desert)
1264 createObjectGroupsByAreas(
1265 new SimpleGroup(mineObjectsBiome.desert[k], true, mineObjectsBiome.tileClass),
1267 avoidCollisionsMines.concat(
1268 [avoidClasses(clFertileLand, 12, mineObjectsBiome.tileClass, 15)]),
1274 for (let i = 0; i < (isNomad() ? scaleByMapSize(6, 16) : scaleByMapSize(0, 8)); ++i)
1276 const mineObjectsBiome = pickRandom(mineObjectsPerBiome);
1277 createObjectGroupsByAreas(
1278 new SimpleGroup(mineObjectsBiome.fertileLand.small, true, mineObjectsBiome.tileClass),
1280 avoidCollisionsMines.concat([
1281 avoidClasses(clDesert, 5, clMetal, 15, clRock, 15, mineObjectsBiome.tileClass, 20)
1288 g_Map.log("Placing triggerpoints for attackers");
1290 new SimpleGroup([new SimpleObject(oTriggerPointAttackerPatrol, 1, 1, 0, 0)], true,
1302 clTriggerPointMap, 15)],
1303 scaleByMapSize(20, 100),
1306 g_Map.log("Creating berries");
1307 createObjectGroupsByAreas(
1308 new SimpleGroup([new SimpleObject(oBerryBushGrapes, 4, 6, 1, 2)], true, clFood),
1311 scaleByMapSize(3, 15),
1315 g_Map.log("Creating rhinos");
1316 createObjectGroupsByAreas(
1317 new SimpleGroup([new SimpleObject(oRhino, 1, 1, 0, 1)], true, clFood),
1320 scaleByMapSize(2, 10),
1324 g_Map.log("Creating warthogs");
1325 createObjectGroupsByAreas(
1326 new SimpleGroup([new SimpleObject(oWarthog, 1, 1, 0, 1)], true, clFood),
1329 scaleByMapSize(2, 10),
1333 g_Map.log("Creating giraffes");
1337 new SimpleObject(oGiraffe, 2, 3, 2, 4),
1338 new SimpleObject(oGiraffeInfant, 2, 3, 2, 4)
1342 scaleByMapSize(2, 10),
1345 g_Map.log("Creating gazelles");
1347 new SimpleGroup([new SimpleObject(oGazelle, 5, 7, 2, 4)], true, clFood),
1350 scaleByMapSize(2, 10),
1356 g_Map.log("Creating lions");
1357 createObjectGroupsByAreas(
1360 new SimpleObject(oLion, 1, 2, 2, 4),
1361 new SimpleObject(oLioness, 2, 3, 2, 4)
1364 [avoidCollisions, avoidClasses(clPlayer, 20)],
1365 scaleByMapSize(2, 10),
1370 g_Map.log("Creating elephants");
1371 createObjectGroupsByAreas(
1374 new SimpleObject(oElephant, 2, 3, 2, 4),
1375 new SimpleObject(oElephantInfant, 2, 3, 2, 4)
1379 scaleByMapSize(2, 10),
1383 g_Map.log("Creating crocodiles");
1385 createObjectGroupsByAreas(
1386 new SimpleGroup([new SimpleObject(oCrocodile, 2, 3, 3, 5)], true, clFood),
1388 [nearWater, avoidCollisions],
1389 scaleByMapSize(1, 6),
1395 g_Map.log("Marking irrigation canal tree area");
1396 const areaIrrigationCanalTrees = createArea(
1397 new MapBoundsPlacer(),
1401 avoidClasses(clPassage, 3),
1405 g_Map.log("Creating irrigation canal trees");
1406 createObjectGroupsByAreas(
1407 new SimpleGroup([new RandomObject(oPalms, 1, 1, 1, 1)], true, clForest),
1409 avoidClasses(clForest, 1),
1410 scaleByMapSize(100, 600),
1412 [areaIrrigationCanalTrees]);
1414 createStragglerTrees(
1416 [stayFertileLand, avoidCollisions],
1418 scaleByMapSize(50, 400),
1421 createStragglerTrees(
1423 [stayDesert, avoidCollisions],
1425 scaleByMapSize(50, 400),
1428 g_Map.log("Placing archer groups on the hilltop");
1429 createObjectGroupsByAreas(
1430 new SimpleGroup([new RandomObject([oKushCitizenArcher, oKushChampionArcher],
1431 scaleByMapSize(4, 10), scaleByMapSize(6, 20), 1, 4)], true, clSoldier),
1433 new StaticConstraint([avoidClasses(clCliff, 1), new NearTileClassConstraint(clCliff, 5)]),
1434 scaleByMapSize(1, 5) / 3 * getDifficulty(),
1438 g_Map.log("Placing individual archers on the hill");
1439 createObjectGroupsByAreas(
1440 new SimpleGroup([new RandomObject([oKushCitizenArcher, oKushChampionArcher], 1, 1, 1, 3)], true,
1443 new StaticConstraint([
1444 new HeightConstraint(heightHillArchers, heightHilltop),
1445 avoidClasses(clCliff, 1, clSoldier, 1),
1446 new NearTileClassConstraint(clCliff, 5)
1448 scaleByMapSize(8, 100) / 3 * getDifficulty(),
1452 g_Map.log("Placing siege engines on the hilltop");
1453 createObjectGroupsByAreas(
1454 new SimpleGroup([new RandomObject(oPtolSiege, 1, 1, 1, 3)], true, clSoldier),
1456 new StaticConstraint(
1458 new NearTileClassConstraint(clCliff, 5),
1459 avoidClasses(clCliff, 1, clSoldier, 1)
1461 scaleByMapSize(1, 6) / 3 * getDifficulty(),
1465 const avoidCollisionsPyramids = new StaticConstraint(
1468 new NearTileClassConstraint(clPyramid, 10)
1472 g_Map.log("Placing soldiers near pyramids");
1473 createObjectGroupsByAreas(
1474 new SimpleGroup([new SimpleObject(oKushCitizenArcher, 1, 1, 1, 1)], true, clSoldier),
1476 avoidCollisionsPyramids,
1477 scaleByMapSize(3, 8),
1481 g_Map.log("Placing treasures at the pyramid");
1482 createObjectGroupsByAreas(
1483 new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
1485 avoidCollisionsPyramids,
1486 scaleByMapSize(1, 10),
1491 g_Map.log("Placing treasures on the hilltop");
1492 createObjectGroupsByAreas(
1493 new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
1495 avoidClasses(clCliff, 1, clTreasure, 1),
1496 scaleByMapSize(8, 35),
1500 g_Map.log("Placing treasures in the city");
1501 const pathBorderConstraint = new AndConstraint([
1502 new StaticConstraint([new NearTileClassConstraint(clCity, 1)]),
1503 avoidClasses(clTreasure, 2, clStatue, 10, clPathStatues, 4, clWall, 2, clForest, 1)
1505 createObjectGroupsByAreas(
1506 new SimpleGroup([new RandomObject(oTreasuresCity, 1, 1, 0, 2)], true, clTreasure),
1508 pathBorderConstraint,
1509 scaleByMapSize(2, 60),
1513 g_Map.log("Placing handcarts on the paths");
1514 createObjectGroupsByAreas(
1515 new SimpleGroup([new SimpleObject(aHandcart, 1, 1, 1, 1)], true, clDecorative),
1517 [pathBorderConstraint, avoidClasses(clDecorative, 10)],
1518 scaleByMapSize(0, 5),
1522 g_Map.log("Placing fence in fertile land");
1523 createObjectGroupsByAreas(
1524 new SimpleGroup([new SimpleObject(aPlotFence, 1, 1, 1, 1)], true, clDecorative),
1526 new StaticConstraint([avoidCollisions, avoidClasses(clWater, 6, clDecorative, 10)]),
1527 scaleByMapSize(1, 10),
1531 g_Map.log("Creating fish");
1533 new SimpleGroup([new SimpleObject(oFish, 3, 4, 2, 3)], true, clFood),
1535 [new StaticConstraint(stayClasses(clWater, 6)), avoidClasses(clFood, 12)],
1536 scaleByMapSize(20, 120),
1541 avoidCollisions = new StaticConstraint(avoidCollisions);
1544 aBushesDesert.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
1545 aBushesDesert.map(bush => scaleByMapSize(20, 120) * randIntInclusive(1, 3)),
1546 [stayDesert, avoidCollisions]);
1549 aBushesFertileLand.map(bush => [new SimpleObject(bush, 0, 4, 2, 4)]),
1550 aBushesFertileLand.map(bush => scaleByMapSize(20, 120) * randIntInclusive(1, 3)),
1551 [stayFertileLand, avoidCollisions]);
1554 [[new SimpleObject(aRock, 0, 4, 2, 4)]],
1555 [[scaleByMapSize(80, 500)]],
1556 [stayDesert, avoidCollisions]);
1559 aBushesFertileLand.map(bush => [new SimpleObject(bush, 0, 3, 2, 4)]),
1560 aBushesFertileLand.map(bush => scaleByMapSize(100, 800)),
1561 [new HeightConstraint(heightWaterLevel, heightShoreline), avoidCollisions]);
1563 g_Map.log("Creating reeds");
1564 createObjectGroupsByAreas(
1565 new SimpleGroup([new RandomObject(aWaterDecoratives, 2, 4, 1, 2)], true),
1567 new StaticConstraint(new NearTileClassConstraint(clFertileLand, 4)),
1568 scaleByMapSize(50, 400),
1572 g_Map.log("Creating reeds at the irrigation canals");
1573 for (const area of areasPassages)
1574 createObjectGroupsByAreas(
1575 new SimpleGroup([new RandomObject(aWaterDecoratives, 2, 4, 1, 2)], true),
1582 g_Map.log("Creating hawk");
1583 for (let i = 0; i < scaleByMapSize(0, 2); ++i)
1584 g_Map.placeEntityAnywhere(oHawk, 0, mapCenter, randomAngle());
1586 placePlayersNomad(clPlayer,
1588 avoidClasses(clHill, 15, clSoldier, 20, clCity, 15, clWall, 20),
1589 avoidCollisionsNomad
1592 setWindAngle(-0.43);
1593 setWaterHeight(heightWaterLevel + SEA_LEVEL);
1594 setWaterTint(0.161, 0.286, 0.353);
1595 setWaterColor(0.129, 0.176, 0.259);
1596 setWaterWaviness(8);
1597 setWaterMurkiness(0.87);
1598 setWaterType("lake");
1600 setAmbientColor(0.58, 0.443, 0.353);
1602 setSunColor(0.733, 0.746, 0.574);
1603 setSunRotation(Math.PI / 2 * randFloat(-1, 1));
1604 setSunElevation(Math.PI / 7);
1608 setFogColor(0.69, 0.616, 0.541);
1611 setPPContrast(0.67);
1612 setPPSaturation(0.42);