Merge 'remotes/trunk'
[0ad.git] / binaries / data / mods / public / maps / random / caledonian_meadows.js
blob3b7b8563a9e1965ae15b87ac195108bd02583345
1 Engine.LoadLibrary("rmgen");
2 Engine.LoadLibrary("rmgen-common");
3 Engine.LoadLibrary("rmbiome");
4 Engine.LoadLibrary("heightmap");
6 function* GenerateMap()
8         const tGrove = "temp_grass_plants";
9         const tPath = "road_rome_a";
11         const oGroveEntities = ["structures/gaul/outpost", "gaia/tree/oak_new"];
13         globalThis.g_Map = new RandomMap(0, "whiteness");
15         /**
16          * Design resource spots
17          */
18         // Mines
19         const decorations = [
20                 "actor|geology/gray1.xml",
21                 "actor|geology/gray_rock1.xml",
22                 "actor|geology/highland1.xml",
23                 "actor|geology/highland2.xml",
24                 "actor|geology/highland3.xml",
25                 "actor|geology/highland_c.xml",
26                 "actor|geology/highland_d.xml",
27                 "actor|geology/highland_e.xml",
28                 "actor|props/flora/bush.xml",
29                 "actor|props/flora/bush_dry_a.xml",
30                 "actor|props/flora/bush_highlands.xml",
31                 "actor|props/flora/bush_tempe_a.xml",
32                 "actor|props/flora/bush_tempe_b.xml",
33                 "actor|props/flora/ferns.xml"
34         ];
36         function placeMine(point, centerEntity)
37         {
38                 g_Map.placeEntityPassable(centerEntity, 0, point, randomAngle());
39                 const quantity = randIntInclusive(11, 23);
40                 const dAngle = 2 * Math.PI / quantity;
42                 for (let i = 0; i < quantity; ++i)
43                         g_Map.placeEntityPassable(
44                                 pickRandom(decorations),
45                                 0,
46                                 Vector2D.add(point,
47                                         new Vector2D(randFloat(2, 5), 0).rotate(-dAngle * randFloat(i, i + 1))),
48                                 randomAngle());
49         }
51         // Food, fences with domestic animals
52         g_WallStyles.other = {
53                 "overlap": 0,
54                 "fence": readyWallElement("structures/fence_long", "gaia"),
55                 "fence_short": readyWallElement("structures/fence_short", "gaia"),
56                 "bench": {
57                         "angle": Math.PI / 2,
58                         "length": 1.5,
59                         "indent": 0,
60                         "bend": 0,
61                         "templateName": "structures/bench"
62                 },
63                 "sheep": {
64                         "angle": 0,
65                         "length": 0,
66                         "indent": 0.75,
67                         "bend": 0,
68                         "templateName": "gaia/fauna_sheep"
69                 },
70                 "foodBin": {
71                         "angle": Math.PI / 2,
72                         "length": 1.5,
73                         "indent": 0,
74                         "bend": 0,
75                         "templateName": "gaia/treasure/food_bin"
76                 },
77                 "farmstead": {
78                         "angle": Math.PI,
79                         "length": 0,
80                         "indent": -3,
81                         "bend": 0,
82                         "templateName": "structures/brit/farmstead"
83                 }
84         };
86         const fences = [
87                 new Fortress("fence", [
88                         "foodBin", "farmstead", "bench",
89                         "turn_0.25", "sheep", "turn_0.25", "fence",
90                         "turn_0.25", "sheep", "turn_0.25", "fence",
91                         "turn_0.25", "sheep", "turn_0.25", "fence"
92                 ]),
93                 new Fortress("fence", [
94                         "foodBin", "farmstead", "fence",
95                         "turn_0.25", "sheep", "turn_0.25", "fence",
96                         "turn_0.25", "sheep", "turn_0.25", "bench", "sheep", "fence",
97                         "turn_0.25", "sheep", "turn_0.25", "fence"
98                 ]),
99                 new Fortress("fence", [
100                         "foodBin", "farmstead", "turn_0.5", "bench", "turn_-0.5", "fence_short",
101                         "turn_0.25", "sheep", "turn_0.25", "fence",
102                         "turn_0.25", "sheep", "turn_0.25", "fence",
103                         "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence"
104                 ]),
105                 new Fortress("fence", [
106                         "foodBin", "farmstead", "turn_0.5", "fence_short", "turn_-0.5", "bench",
107                         "turn_0.25", "sheep", "turn_0.25", "fence",
108                         "turn_0.25", "sheep", "turn_0.25", "fence",
109                         "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence"
110                 ]),
111                 new Fortress("fence", [
112                         "foodBin", "farmstead", "fence",
113                         "turn_0.25", "sheep", "turn_0.25", "bench", "sheep", "fence",
114                         "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence",
115                         "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence"
116                 ])
117         ].flatMap(fence => [fence, new Fortress("fence", clone(fence.wall).reverse())]);
119         // Groves, only wood
120         const groveEntities = ["gaia/tree/bush_temperate", "gaia/tree/euro_beech"];
121         const groveActors = [
122                 "actor|geology/highland1_moss.xml",
123                 "actor|geology/highland2_moss.xml",
124                 "actor|props/flora/bush.xml",
125                 "actor|props/flora/bush_dry_a.xml",
126                 "actor|props/flora/bush_highlands.xml",
127                 "actor|props/flora/bush_tempe_a.xml",
128                 "actor|props/flora/bush_tempe_b.xml",
129                 "actor|props/flora/ferns.xml"
130         ];
132         function placeGrove(point)
133         {
134                 g_Map.placeEntityPassable(pickRandom(oGroveEntities), 0, point, randomAngle());
135                 const quantity = randIntInclusive(20, 30);
136                 const dAngle = 2 * Math.PI / quantity;
137                 for (let i = 0; i < quantity; ++i)
138                 {
139                         const angle = dAngle * randFloat(i, i + 1);
140                         const dist = randFloat(2, 5);
141                         let objectList = groveEntities;
142                         if (i % 3 == 0)
143                                 objectList = groveActors;
144                         const position = Vector2D.add(point, new Vector2D(dist, 0).rotate(-angle));
145                         g_Map.placeEntityPassable(pickRandom(objectList), 0, position, randomAngle());
146                         createArea(
147                                 new ClumpPlacer(5, 1, 1, Infinity, position),
148                                 new TerrainPainter(tGrove));
149                 }
150         }
152         // Camps with fire and gold treasure
153         function placeCamp(point)
154         {
155                 const centerEntity = "actor|props/special/eyecandy/campfire.xml";
156                 const otherEntities = [
157                         "gaia/treasure/metal",
158                         "gaia/treasure/standing_stone",
159                         "units/brit/infantry_slinger_b",
160                         "units/brit/infantry_javelineer_b",
161                         "units/gaul/infantry_slinger_b",
162                         "units/gaul/infantry_javelineer_b",
163                         "units/gaul/champion_fanatic",
164                         "actor|props/special/common/waypoint_flag.xml",
165                         "actor|props/special/eyecandy/barrel_a.xml",
166                         "actor|props/special/eyecandy/basket_celt_a.xml",
167                         "actor|props/special/eyecandy/crate_a.xml",
168                         "actor|props/special/eyecandy/dummy_a.xml",
169                         "actor|props/special/eyecandy/handcart_1.xml",
170                         "actor|props/special/eyecandy/handcart_1_broken.xml",
171                         "actor|props/special/eyecandy/sack_1.xml",
172                         "actor|props/special/eyecandy/sack_1_rough.xml"
173                 ];
174                 g_Map.placeEntityPassable(centerEntity, 0, point, randomAngle());
175                 const quantity = randIntInclusive(5, 11);
176                 const dAngle = 2 * Math.PI / quantity;
177                 for (let i = 0; i < quantity; ++i)
178                 {
179                         const angle = dAngle * randFloat(i, i + 1);
180                         const dist = randFloat(1, 3);
181                         g_Map.placeEntityPassable(pickRandom(otherEntities), 0,
182                                 Vector2D.add(point, new Vector2D(dist, 0).rotate(-angle)), randomAngle());
183                 }
184         }
186         function placeStartLocationResources(point)
187         {
188                 const foodEntities = ["gaia/fruit/berry_01", "gaia/fauna_chicken", "gaia/fauna_chicken"];
189                 let currentAngle = randomAngle();
190                 // Stone and chicken
191                 let dAngle = 4/9 * Math.PI;
192                 let angle = currentAngle + randFloat(1, 3) * dAngle / 4;
193                 const stonePosition = Vector2D.add(point, new Vector2D(12, 0).rotate(-angle));
194                 placeMine(stonePosition, "gaia/rock/temperate_large");
195                 currentAngle += dAngle;
197                 // Wood
198                 let quantity = 80;
199                 dAngle = 2 * Math.PI / quantity / 3;
200                 for (let i = 0; i < quantity; ++i)
201                 {
202                         angle = currentAngle + randFloat(0, dAngle);
203                         let objectList = groveEntities;
204                         if (i % 2 == 0)
205                                 objectList = groveActors;
206                         const woodPosition =
207                                 Vector2D.add(point, new Vector2D(randFloat(10, 15), 0).rotate(-angle));
208                         g_Map.placeEntityPassable(pickRandom(objectList), 0, woodPosition, randomAngle());
209                         createArea(
210                                 new ClumpPlacer(5, 1, 1, Infinity, woodPosition),
211                                 new TerrainPainter("temp_grass_plants"));
212                         currentAngle += dAngle;
213                 }
215                 // Metal and chicken
216                 dAngle = 2 * Math.PI * 2 / 9;
217                 angle = currentAngle + dAngle * randFloat(1, 3) / 4;
218                 const metalPosition = Vector2D.add(point, new Vector2D(13, 0).rotate(-angle));
219                 placeMine(metalPosition, "gaia/ore/temperate_large");
220                 currentAngle += dAngle;
222                 // Berries
223                 quantity = 15;
224                 dAngle = 2 * Math.PI / quantity * 2 / 9;
225                 for (let i = 0; i < quantity; ++i)
226                 {
227                         angle = currentAngle + randFloat(0, dAngle);
228                         const berriesPosition =
229                                 Vector2D.add(point, new Vector2D(randFloat(10, 15), 0).rotate(-angle));
230                         g_Map.placeEntityPassable(pickRandom(foodEntities), 0, berriesPosition, randomAngle());
231                         currentAngle += dAngle;
232                 }
233         }
235         /**
236          * Environment settings
237          */
238         setBiome("generic/alpine");
239         g_Environment.Fog.FogColor = { "r": 0.8, "g": 0.8, "b": 0.8, "a": 0.01 };
240         g_Environment.Water.WaterBody.Colour = { "r": 0.3, "g": 0.05, "b": 0.1, "a": 0.1 };
241         g_Environment.Water.WaterBody.Murkiness = 0.4;
243         /**
244          * Base terrain shape generation and settings
245          */
246         const heightScale = (g_Map.size + 256) / 768 / 4;
247         const heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale };
249         // Water coverage
250         // NOTE: Since terrain generation is quite unpredictable actual water
251         // coverage might vary much with the same value
252         const averageWaterCoverage = 1 / 5;
253         // Water height in environment and the engine
254         const heightSeaGround = -MIN_HEIGHT + heightRange.min + averageWaterCoverage *
255                 (heightRange.max - heightRange.min);
256         // Water height in RMGEN
257         const heightSeaGroundAdjusted = heightSeaGround + MIN_HEIGHT;
258         setWaterHeight(heightSeaGround);
260         g_Map.log("Generating terrain using diamon-square");
261         const medH = (heightRange.min + heightRange.max) / 2;
262         const initialHeightmap = [[medH, medH], [medH, medH]];
263         setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8);
265         g_Map.log("Apply erosion");
266         for (let i = 0; i < 5; ++i)
267                 splashErodeMap(0.1);
269         rescaleHeightmap(heightRange.min, heightRange.max);
271         yield 25;
273         const heighLimits = [
274                 // 0 Deep water
275                 [true, 1 / 3],
276                 // 1 Medium Water
277                 [true, 2 / 3],
278                 // 2 Shallow water
279                 [true, 3 / 3],
280                 // 3 Shore
281                 [false, 1 / 8],
282                 // 4 Low ground
283                 [false, 2 / 8],
284                 // 5 Player and path height
285                 [false, 3 / 8],
286                 // 6 High ground
287                 [false, 4 / 8],
288                 // 7 Lower forest border
289                 [false, 5 / 8],
290                 // 8 Forest
291                 [false, 6 / 8],
292                 // 9 Upper forest border
293                 [false, 7 / 8],
294                 // 10 Hilltop
295                 [false, 8 / 8]
296         ].map(([underWater, ratio]) => {
297                 const base = underWater ? heightRange.min : heightSeaGround;
298                 const factor = underWater ? heightSeaGroundAdjusted - heightRange.min :
299                         heightRange.max - heightSeaGroundAdjusted;
300                 return base + ratio * factor;
301         });
303         const playerHeight = (heighLimits[4] + heighLimits[5]) / 2; // Average player height
305         g_Map.log("Determining height-dependent biome");
306         // Texture and actor presets
307         const myBiome = [
308                 // 0 Deep water
309                 {
310                         "flat": {
311                                 "texture": ["shoreline_stoney_a"],
312                                 "entity": ["gaia/fish/generic", "actor|geology/stone_granite_boulder.xml"],
313                                 "entityPropability": 0.02
314                         },
315                         "steep": {
316                                 "texture": ["alpine_mountainside"],
317                                 "entity": ["gaia/fish/generic"],
318                                 "entityPropability": 0.1
319                         }
320                 },
322                 // 1 Medium Water
323                 {
324                         "flat": {
325                                 "texture": ["shoreline_stoney_a", "alpine_shore_rocks"],
326                                 "entity":
327                                         [
328                                                 "actor|geology/stone_granite_boulder.xml",
329                                                 "actor|geology/stone_granite_med.xml"
330                                         ],
331                                 "entityPropability": 0.03
332                         },
333                         "steep": {
334                                 "texture": ["alpine_mountainside"],
335                                 "entity":
336                                         [
337                                                 "actor|geology/stone_granite_boulder.xml",
338                                                 "actor|geology/stone_granite_med.xml"
339                                         ],
340                                 "entityPropability": 0.0
341                         }
342                 },
344                 // 2 Shallow water
345                 {
346                         "flat": {
347                                 "texture": ["alpine_shore_rocks"],
348                                 "entity": [
349                                         "actor|props/flora/reeds_pond_dry.xml",
350                                         "actor|geology/stone_granite_large.xml",
351                                         "actor|geology/stone_granite_med.xml",
352                                         "actor|props/flora/reeds_pond_lush_b.xml"
353                                 ],
354                                 "entityPropability": 0.2
355                         },
356                         "steep": {
357                                 "texture": ["alpine_mountainside"],
358                                 "entity":
359                                         [
360                                                 "actor|props/flora/reeds_pond_dry.xml",
361                                                 "actor|geology/stone_granite_med.xml"
362                                         ],
363                                 "entityPropability": 0.1
364                         }
365                 },
367                 // 3 Shore
368                 {
369                         "flat": {
370                                 "texture": ["alpine_shore_rocks_grass_50", "alpine_grass_rocky"],
371                                 "entity": [
372                                         "gaia/tree/pine",
373                                         "gaia/tree/bush_badlands",
374                                         "actor|geology/highland1_moss.xml",
375                                         "actor|props/flora/grass_soft_tuft_a.xml",
376                                         "actor|props/flora/bush.xml"
377                                 ],
378                                 "entityPropability": 0.3
379                         },
380                         "steep": {
381                                 "texture": ["alpine_mountainside"],
382                                 "entity": ["actor|props/flora/grass_soft_tuft_a.xml"],
383                                 "entityPropability": 0.1
384                         }
385                 },
387                 // 4 Low ground
388                 {
389                         "flat": {
390                                 "texture": ["alpine_dirt_grass_50", "alpine_grass_rocky"],
391                                 "entity": [
392                                         "actor|geology/stone_granite_med.xml",
393                                         "actor|props/flora/grass_soft_tuft_a.xml",
394                                         "actor|props/flora/bush.xml",
395                                         "actor|props/flora/grass_medit_flowering_tall.xml"
396                                 ],
397                                 "entityPropability": 0.2
398                         },
399                         "steep": {
400                                 "texture": ["alpine_grass_rocky"],
401                                 "entity":
402                                         [
403                                                 "actor|geology/stone_granite_med.xml",
404                                                 "actor|props/flora/grass_soft_tuft_a.xml"
405                                         ],
406                                 "entityPropability": 0.1
407                         }
408                 },
410                 // 5 Player and path height
411                 {
412                         "flat": {
413                                 "texture": ["new_alpine_grass_c", "new_alpine_grass_b", "new_alpine_grass_d"],
414                                 "entity": [
415                                         "actor|geology/stone_granite_small.xml",
416                                         "actor|props/flora/grass_soft_small.xml",
417                                         "actor|props/flora/grass_medit_flowering_tall.xml"
418                                 ],
419                                 "entityPropability": 0.2
420                         },
421                         "steep": {
422                                 "texture": ["alpine_grass_rocky"],
423                                 "entity":
424                                         [
425                                                 "actor|geology/stone_granite_small.xml",
426                                                 "actor|props/flora/grass_soft_small.xml"
427                                         ],
428                                 "entityPropability": 0.1
429                         }
430                 },
432                 // 6 High ground
433                 {
434                         "flat": {
435                                 "texture": ["new_alpine_grass_a", "alpine_grass_rocky"],
436                                 "entity": [
437                                         "actor|geology/stone_granite_med.xml",
438                                         "actor|props/flora/grass_tufts_a.xml",
439                                         "actor|props/flora/bush_highlands.xml",
440                                         "actor|props/flora/grass_medit_flowering_tall.xml"
441                                 ],
442                                 "entityPropability": 0.2
443                         },
444                         "steep": {
445                                 "texture": ["alpine_grass_rocky"],
446                                 "entity":
447                                         [
448                                                 "actor|geology/stone_granite_med.xml",
449                                                 "actor|props/flora/grass_tufts_a.xml"
450                                         ],
451                                 "entityPropability": 0.1
452                         }
453                 },
455                 // 7 Lower forest border
456                 {
457                         "flat": {
458                                 "texture": ["new_alpine_grass_mossy", "alpine_grass_rocky"],
459                                 "entity": [
460                                         "gaia/tree/pine",
461                                         "gaia/tree/oak",
462                                         "actor|props/flora/grass_tufts_a.xml",
463                                         "gaia/fruit/berry_01",
464                                         "actor|geology/highland2_moss.xml",
465                                         "gaia/fauna_goat",
466                                         "actor|props/flora/bush_tempe_underbrush.xml"
467                                 ],
468                                 "entityPropability": 0.3
469                         },
470                         "steep": {
471                                 "texture": ["alpine_cliff_c"],
472                                 "entity":
473                                         [
474                                                 "actor|props/flora/grass_tufts_a.xml",
475                                                 "actor|geology/highland2_moss.xml"
476                                         ],
477                                 "entityPropability": 0.1
478                         }
479                 },
481                 // 8 Forest
482                 {
483                         "flat": {
484                                 "texture": ["alpine_forrestfloor"],
485                                 "entity": [
486                                         "gaia/tree/pine",
487                                         "gaia/tree/pine",
488                                         "gaia/tree/pine",
489                                         "gaia/tree/pine",
490                                         "actor|geology/highland2_moss.xml",
491                                         "actor|props/flora/bush_highlands.xml"
492                                 ],
493                                 "entityPropability": 0.5
494                         },
495                         "steep": {
496                                 "texture": ["alpine_cliff_c"],
497                                 "entity": [
498                                         "actor|geology/highland2_moss.xml", "actor|geology/stone_granite_med.xml"
499                                 ],
500                                 "entityPropability": 0.1
501                         }
502                 },
504                 // 9 Upper forest border
505                 {
506                         "flat": {
507                                 "texture": ["alpine_forrestfloor_snow", "new_alpine_grass_dirt_a"],
508                                 "entity": ["gaia/tree/pine", "actor|geology/snow1.xml"],
509                                 "entityPropability": 0.3
510                         },
511                         "steep": {
512                                 "texture": ["alpine_cliff_b"],
513                                 "entity": ["actor|geology/stone_granite_med.xml", "actor|geology/snow1.xml"],
514                                 "entityPropability": 0.1
515                         }
516                 },
518                 // 10 Hilltop
519                 {
520                         "flat": {
521                                 "texture": ["alpine_cliff_a", "alpine_cliff_snow"],
522                                 "entity": ["actor|geology/highland1.xml"],
523                                 "entityPropability": 0.05
524                         },
525                         "steep": {
526                                 "texture": ["alpine_cliff_c"],
527                                 "entity": ["actor|geology/highland1.xml"],
528                                 "entityPropability": 0.0
529                         }
530                 }
531         ];
533         const [playerIDs, playerPosition] = groupPlayersCycle(
534                 getStartLocationsByHeightmap({ "min": heighLimits[4], "max": heighLimits[5] }, 1000, 30));
535         yield 30;
537         g_Map.log("Smoothing player locations");
538         for (const position of playerPosition)
539                 createArea(
540                         new DiskPlacer(35, position),
541                         new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 35));
543         g_Map.log("Creating paths between players");
544         const clPath = g_Map.createTileClass();
545         for (let i = 0; i < playerPosition.length; ++i)
546                 createArea(
547                         new RandomPathPlacer(playerPosition[i],
548                                 playerPosition[(i + 1) % playerPosition.length], 4, 2, false),
549                         [
550                                 new TerrainPainter(tPath),
551                                 new ElevationBlendingPainter(playerHeight, 0.4),
552                                 new TileClassPainter(clPath)
553                         ]);
555         g_Map.log("Smoothing paths");
556         createArea(
557                 new MapBoundsPlacer(),
558                 new SmoothingPainter(5, 1, 1),
559                 new NearTileClassConstraint(clPath, 5));
561         yield 45;
563         g_Map.log("Determining resource locations");
564         const avoidPoints = playerPosition.map(pos => pos.clone());
565         avoidPoints.forEach(point => { point.dist = 30; });
566         const resourceSpots = getPointsByHeight(
567                 {
568                         "min": (heighLimits[3] + heighLimits[4]) / 2,
569                         "max": (heighLimits[5] + heighLimits[6]) / 2
570                 },
571                 avoidPoints,
572                 clPath);
573         yield 55;
575         /**
576          * Divide tiles in areas by height and avoid paths
577          */
578         const tchm = getTileCenteredHeightmap();
579         const areas = heighLimits.map(heightLimit => []);
580         for (let x = 0; x < tchm.length; ++x)
581                 for (let y = 0; y < tchm[0].length; ++y)
582                 {
583                         const position = new Vector2D(x, y);
584                         if (!avoidClasses(clPath, 0).allows(position) || tchm[x][y] < heightRange.min)
585                                 continue;
587                         const index = heighLimits.findIndex(limit => tchm[x][y] <= limit);
588                         if (index !== -1)
589                                 areas[index].push(position);
590                 }
592         /**
593          * Get midpoint slope of each area
594          */
595         const slopeMap = getSlopeMap();
596         const slopeMidpoints = areas.map(area => {
597                 const slopesInThisArea = area.map(({ x, y }) => slopeMap[x][y]);
598                 return Math.min(...slopesInThisArea) + Math.max(...slopesInThisArea);
599         });
601         g_Map.log("Painting areas by height and slope");
602         for (let h = 0; h < heighLimits.length; ++h)
603                 for (const point of areas[h])
604                 {
605                         const isFlat = slopeMap[point.x][point.y] < 0.4 * slopeMidpoints[h];
606                         const selectedBiome = myBiome[h][isFlat? "flat" : "steep"];
608                         g_Map.setTexture(point, pickRandom(selectedBiome.texture));
610                         if (randBool(selectedBiome.entityPropability))
611                                 g_Map.placeEntityPassable(pickRandom(selectedBiome.entity), 0,
612                                         randomPositionOnTile(point), randomAngle());
613                 }
614         yield 80;
616         g_Map.log("Placing players");
617         if (isNomad())
618                 placePlayersNomad(g_Map.createTileClass(),
619                         new HeightConstraint(heighLimits[4], heighLimits[5]));
620         else
621                 for (let p = 0; p < playerIDs.length; ++p)
622                 {
623                         placeCivDefaultStartingEntities(playerPosition[p], playerIDs[p], true);
624                         placeStartLocationResources(playerPosition[p]);
625                 }
627         g_Map.log("Placing resources, farmsteads, groves and camps");
628         for (let i = 0; i < resourceSpots.length; ++i)
629         {
630                 const pos = new Vector2D(resourceSpots[i].x, resourceSpots[i].y);
631                 const choice = i % (isNomad() ? 4 : 5);
632                 if (choice == 0)
633                         placeMine(pos, "gaia/rock/temperate_large_02");
634                 if (choice == 1)
635                         placeMine(pos, "gaia/ore/temperate_large");
636                 if (choice == 2)
637                         placeCustomFortress(pos, pickRandom(fences), "other", 0, randomAngle());
638                 if (choice == 3)
639                         placeGrove(pos);
640                 if (choice == 4)
641                         placeCamp(pos);
642         }
644         return g_Map;