1 RMS.LoadLibrary("rmgen");
2 RMS.LoadLibrary("rmbiome");
3 RMS.LoadLibrary("heightmap");
7 let genStartTime = Date.now();
10 * getArray - To ensure a terrain texture is contained within an array
12 function getArray(stringOrArrayOfStrings)
14 if (typeof stringOrArrayOfStrings == "string")
15 return [stringOrArrayOfStrings];
16 return stringOrArrayOfStrings;
21 // Terrain, entities and actors
25 "texture": getArray(g_Terrains.water),
26 "actor": [[g_Gaia.fish], 0.01],
27 "textureHS": getArray(g_Terrains.water),
28 "actorHS": [[g_Gaia.fish], 0.03]
32 "texture": getArray(g_Terrains.water),
33 "actor": [[g_Decoratives.lillies, g_Decoratives.reeds], 0.3],
34 "textureHS": getArray(g_Terrains.water),
35 "actorHS": [[g_Decoratives.lillies], 0.1]
39 "texture": getArray(g_Terrains.shore),
42 g_Gaia.tree1, g_Gaia.tree1,
43 g_Gaia.tree2, g_Gaia.tree2,
44 g_Gaia.mainHuntableAnimal,
45 g_Decoratives.grass, g_Decoratives.grass,
46 g_Decoratives.rockMedium, g_Decoratives.rockMedium,
47 g_Decoratives.bushMedium, g_Decoratives.bushMedium
51 "textureHS": getArray(g_Terrains.cliff),
52 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
56 "texture": getArray(g_Terrains.tier1Terrain),
60 g_Decoratives.grassShort,
61 g_Decoratives.rockLarge,
62 g_Decoratives.rockMedium,
63 g_Decoratives.bushMedium,
64 g_Decoratives.bushSmall
68 "textureHS": getArray(g_Terrains.cliff),
69 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
71 // 4 Mid ground. Player and path height
73 "texture": getArray(g_Terrains.mainTerrain),
77 g_Decoratives.grassShort,
78 g_Decoratives.rockLarge,
79 g_Decoratives.rockMedium,
80 g_Decoratives.bushMedium,
81 g_Decoratives.bushSmall
85 "textureHS": getArray(g_Terrains.cliff),
86 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
90 "texture": getArray(g_Terrains.tier2Terrain),
94 g_Decoratives.grassShort,
95 g_Decoratives.rockLarge,
96 g_Decoratives.rockMedium,
97 g_Decoratives.bushMedium,
98 g_Decoratives.bushSmall
102 "textureHS": getArray(g_Terrains.cliff),
103 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
105 // 6 Lower hilltop forest border
107 "texture": getArray(g_Terrains.dirt),
113 g_Gaia.secondaryHuntableAnimal,
115 g_Decoratives.rockMedium,
116 g_Decoratives.bushMedium
120 "textureHS": getArray(g_Terrains.cliff),
121 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
125 "texture": getArray(g_Terrains.forestFloor1),
135 g_Decoratives.rockMedium,
136 g_Decoratives.bushMedium
140 "textureHS": getArray(g_Terrains.cliff),
141 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
145 var mercenaryCampGuards = {
147 { "Template" : "structures/merc_camp_egyptian" },
148 { "Template" : "units/mace_infantry_javelinist_b", "Count" : 4 },
149 { "Template" : "units/mace_cavalry_spearman_e", "Count" : 3 },
150 { "Template" : "units/mace_infantry_archer_a", "Count" : 4 },
151 { "Template" : "units/mace_champion_infantry_a", "Count" : 3 }
154 { "Template" : "structures/ptol_mercenary_camp" },
155 { "Template" : "units/brit_infantry_javelinist_b", "Count" : 4 },
156 { "Template" : "units/brit_cavalry_swordsman_e", "Count" : 3 },
157 { "Template" : "units/brit_infantry_slinger_a", "Count" : 4 },
158 { "Template" : "units/brit_champion_infantry", "Count" : 3 }
161 { "Template" : "structures/ptol_mercenary_camp" },
162 { "Template" : "units/pers_infantry_javelinist_b", "Count" : 4 },
163 { "Template" : "units/pers_cavalry_swordsman_e", "Count" : 3 },
164 { "Template" : "units/pers_infantry_archer_a", "Count" : 4 },
165 { "Template" : "units/pers_champion_infantry", "Count" : 3 }
168 { "Template" : "structures/ptol_mercenary_camp" },
169 { "Template" : "units/rome_infantry_swordsman_b", "Count" : 4 },
170 { "Template" : "units/rome_cavalry_spearman_e", "Count" : 3 },
171 { "Template" : "units/rome_infantry_javelinist_a", "Count" : 4 },
172 { "Template" : "units/rome_champion_infantry", "Count" : 3 }
175 { "Template" : "structures/merc_camp_egyptian" },
176 { "Template" : "units/iber_infantry_javelinist_b", "Count" : 4 },
177 { "Template" : "units/iber_cavalry_spearman_e", "Count" : 3 },
178 { "Template" : "units/iber_infantry_slinger_a", "Count" : 4 },
179 { "Template" : "units/iber_champion_infantry", "Count" : 3 }
182 { "Template" : "structures/merc_camp_egyptian" },
183 { "Template" : "units/sele_infantry_javelinist_b", "Count" : 4 },
184 { "Template" : "units/sele_cavalry_spearman_merc_e", "Count" : 3 },
185 { "Template" : "units/sele_infantry_spearman_a", "Count" : 4 },
186 { "Template" : "units/sele_champion_infantry_swordsman", "Count" : 3 }
189 { "Template" : "structures/merc_camp_egyptian" },
190 { "Template" : "units/ptol_infantry_javelinist_b", "Count" : 4 },
191 { "Template" : "units/ptol_cavalry_archer_e", "Count" : 3 },
192 { "Template" : "units/ptol_infantry_slinger_a", "Count" : 4 },
193 { "Template" : "units/ptol_champion_infantry_pikeman", "Count" : 3 }
196 { "Template" : "structures/ptol_mercenary_camp" },
197 { "Template" : "units/gaul_infantry_javelinist_b", "Count" : 4 },
198 { "Template" : "units/gaul_cavalry_swordsman_e", "Count" : 3 },
199 { "Template" : "units/gaul_infantry_slinger_a", "Count" : 4 },
200 { "Template" : "units/gaul_champion_infantry", "Count" : 3 }
205 * Resource spots and other points of interest
209 function placeMine(point, centerEntity,
211 g_Decoratives.grass, g_Decoratives.grassShort,
212 g_Decoratives.rockLarge, g_Decoratives.rockMedium,
213 g_Decoratives.bushMedium, g_Decoratives.bushSmall
217 placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI));
218 let quantity = randIntInclusive(11, 23);
219 let dAngle = TWO_PI / quantity;
220 for (let i = 0; i < quantity; ++i)
222 let angle = dAngle * randFloat(i, i + 1);
223 let dist = randFloat(2, 5);
224 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(decorativeActors), 0, randFloat(0, 2 * PI));
229 let groveActors = [g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium];
230 let clGrove = createTileClass();
232 function placeGrove(point,
234 g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1,
235 g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2,
236 g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree3,
237 g_Gaia.tree4, g_Gaia.tree4, g_Gaia.tree5
239 groveActors = [g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium], groveTileClass = undefined,
240 groveTerrainTexture = getArray(g_Terrains.forestFloor1)
243 placeObject(point.x, point.y, pickRandom(["structures/gaul_outpost", "gaia/flora_tree_oak_new"]), 0, randFloat(0, 2 * PI));
244 let quantity = randIntInclusive(20, 30);
245 let dAngle = TWO_PI / quantity;
246 for (let i = 0; i < quantity; ++i)
248 let angle = dAngle * randFloat(i, i + 1);
249 let dist = randFloat(2, 5);
250 let objectList = groveEntities;
252 objectList = groveActors;
253 let x = point.x + dist * Math.cos(angle);
254 let y = point.y + dist * Math.sin(angle);
255 placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * PI));
257 createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter(groveTerrainTexture), paintClass(groveTileClass)]);
259 createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter(groveTerrainTexture)]);
264 "temperate": { "building": "structures/mace_farmstead", "animal": "gaia/fauna_pig" },
265 "snowy": { "building": "structures/brit_farmstead", "animal": "gaia/fauna_sheep" },
266 "desert": { "building": "structures/pers_farmstead", "animal": "gaia/fauna_camel" },
267 "alpine": { "building": "structures/rome_farmstead", "animal": "gaia/fauna_sheep" },
268 "mediterranean": { "building": "structures/iber_farmstead", "animal": "gaia/fauna_pig" },
269 "savanna": { "building": "structures/sele_farmstead", "animal": "gaia/fauna_horse" },
270 "tropic": { "building": "structures/ptol_farmstead", "animal": "gaia/fauna_camel" },
271 "autumn": { "building": "structures/gaul_farmstead", "animal": "gaia/fauna_horse" }
274 wallStyles.other.sheepIn = new WallElement("sheepIn", farmEntities[currentBiome()].animal, PI / 4, -1.5, 0.75, PI/2);
275 wallStyles.other.foodBin = new WallElement("foodBin", "gaia/special_treasure_food_bin", PI/2, 1.5);
276 wallStyles.other.sheep = new WallElement("sheep", farmEntities[currentBiome()].animal, 0, 0, 0.75);
277 wallStyles.other.farm = new WallElement("farm", farmEntities[currentBiome()].building, PI, 0, -3);
279 new Fortress("fence", ["foodBin", "farm", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence"]),
280 new Fortress("fence", ["foodBin", "farm", "fence", "sheepIn", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence"]),
281 new Fortress("fence", [
282 "foodBin", "farm", "cornerIn", "bench", "cornerOut", "fence_short", "sheepIn", "fence", "sheepIn",
283 "fence", "sheepIn", "fence_short", "sheep", "fence"
285 new Fortress("fence", [
286 "foodBin", "farm", "cornerIn", "fence_short", "cornerOut", "bench", "sheepIn", "fence", "sheepIn",
287 "fence", "sheepIn", "fence_short", "sheep", "fence"
289 new Fortress("fence", [
290 "foodBin", "farm", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn",
291 "fence_short", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence"
294 let num = fences.length;
295 for (let i = 0; i < num; ++i)
296 fences.push(new Fortress("fence", clone(fences[i].wall).reverse()));
298 // Camps with fire and gold treasure
299 function placeCamp(point,
300 centerEntity = "actor|props/special/eyecandy/campfire.xml",
301 otherEntities = ["gaia/special_treasure_metal", "gaia/special_treasure_standing_stone",
302 "units/brit_infantry_slinger_b", "units/brit_infantry_javelinist_b", "units/gaul_infantry_slinger_b", "units/gaul_infantry_javelinist_b", "units/gaul_champion_fanatic",
303 "actor|props/special/common/waypoint_flag.xml", "actor|props/special/eyecandy/barrel_a.xml", "actor|props/special/eyecandy/basket_celt_a.xml", "actor|props/special/eyecandy/crate_a.xml", "actor|props/special/eyecandy/dummy_a.xml", "actor|props/special/eyecandy/handcart_1.xml", "actor|props/special/eyecandy/handcart_1_broken.xml", "actor|props/special/eyecandy/sack_1.xml", "actor|props/special/eyecandy/sack_1_rough.xml"
307 placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI));
308 let quantity = randIntInclusive(5, 11);
309 let dAngle = TWO_PI / quantity;
310 for (let i = 0; i < quantity; ++i)
312 let angle = dAngle * randFloat(i, i + 1);
313 let dist = randFloat(1, 3);
314 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(otherEntities), 0, randFloat(0, 2 * PI));
318 function placeStartLocationResources(
320 foodEntities = [g_Gaia.fruitBush, g_Gaia.chicken],
322 g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1,
323 g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2,
324 g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree3,
325 g_Gaia.tree4, g_Gaia.tree4, g_Gaia.tree5
327 groveTerrainTexture = getArray(g_Terrains.forestFloor1),
328 averageDistToCC = 10,
332 function getRandDist()
334 return averageDistToCC + randFloat(-dAverageDistToCC, dAverageDistToCC);
337 let currentAngle = randFloat(0, TWO_PI);
339 let dAngle = TWO_PI * 2 / 9;
340 let angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
341 placeMine({ "x": point.x + averageDistToCC * Math.cos(angle), "y": point.y + averageDistToCC * Math.sin(angle) }, g_Gaia.stoneLarge);
343 currentAngle += dAngle;
347 dAngle = TWO_PI / quantity / 3;
348 for (let i = 0; i < quantity; ++i)
350 angle = currentAngle + randFloat(0, dAngle);
351 let dist = getRandDist();
352 let objectList = groveEntities;
354 objectList = groveActors;
355 let x = point.x + dist * Math.cos(angle);
356 let y = point.y + dist * Math.sin(angle);
357 placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * PI));
358 createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter(groveTerrainTexture), paintClass(clGrove)]);
359 currentAngle += dAngle;
363 dAngle = TWO_PI * 2 / 9;
364 angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
365 placeMine({ "x": point.x + averageDistToCC * Math.cos(angle), "y": point.y + averageDistToCC * Math.sin(angle) }, g_Gaia.metalLarge);
366 currentAngle += dAngle;
368 // Berries and domestic animals
370 dAngle = TWO_PI / quantity * 2 / 9;
371 for (let i = 0; i < quantity; ++i)
373 angle = currentAngle + randFloat(0, dAngle);
374 let dist = getRandDist();
375 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(foodEntities), 0, randFloat(0, 2 * PI));
376 currentAngle += dAngle;
380 log("Functions loaded after " + ((Date.now() - genStartTime) / 1000) + "s");
383 * Base terrain shape generation and settings
385 // Height range by map size
386 let heightScale = (g_Map.size + 256) / 768 / 4;
387 let heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale };
390 let averageWaterCoverage = 1/5; // NOTE: Since terrain generation is quite unpredictable actual water coverage might vary much with the same value
391 let waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); // Water height in environment and the engine
392 let waterHeightAdjusted = waterHeight + MIN_HEIGHT; // Water height as terrain height
393 setWaterHeight(waterHeight);
395 // Generate base terrain shape
396 let lowH = heightRange.min;
397 let medH = (heightRange.min + heightRange.max) / 2;
400 let initialHeightmap = [
401 [medH, medH, medH, medH, medH, medH],
402 [medH, medH, medH, medH, medH, medH],
403 [medH, medH, lowH, lowH, medH, medH],
404 [medH, medH, lowH, lowH, medH, medH],
405 [medH, medH, medH, medH, medH, medH],
406 [medH, medH, medH, medH, medH, medH],
408 if (g_Map.size < 256)
411 [medH, medH, medH, medH, medH],
412 [medH, medH, medH, medH, medH],
413 [medH, medH, lowH, medH, medH],
414 [medH, medH, medH, medH, medH],
415 [medH, medH, medH, medH, medH]
418 if (g_Map.size >= 384)
421 [medH, medH, medH, medH, medH, medH, medH, medH],
422 [medH, medH, medH, medH, medH, medH, medH, medH],
423 [medH, medH, medH, medH, medH, medH, medH, medH],
424 [medH, medH, medH, lowH, lowH, medH, medH, medH],
425 [medH, medH, medH, lowH, lowH, medH, medH, medH],
426 [medH, medH, medH, medH, medH, medH, medH, medH],
427 [medH, medH, medH, medH, medH, medH, medH, medH],
428 [medH, medH, medH, medH, medH, medH, medH, medH],
432 setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8);
434 // Apply simple erosion
435 for (let i = 0; i < 5; ++i)
437 globalSmoothHeightmap();
440 rescaleHeightmap(heightRange.min, heightRange.max);
445 * Prepare terrain texture placement
448 heightRange.min + 3/4 * (waterHeightAdjusted - heightRange.min), // 0 Deep water
449 waterHeightAdjusted, // 1 Shallow water
450 waterHeightAdjusted + 2/8 * (heightRange.max - waterHeightAdjusted), // 2 Shore
451 waterHeightAdjusted + 3/8 * (heightRange.max - waterHeightAdjusted), // 3 Low ground
452 waterHeightAdjusted + 4/8 * (heightRange.max - waterHeightAdjusted), // 4 Player and path height
453 waterHeightAdjusted + 6/8 * (heightRange.max - waterHeightAdjusted), // 5 High ground
454 waterHeightAdjusted + 7/8 * (heightRange.max - waterHeightAdjusted), // 6 Lower forest border
455 heightRange.max // 7 Forest
457 let playerHeightRange = { "min" : heighLimits[3], "max" : heighLimits[4] };
458 let resourceSpotHeightRange = { "min" : (heighLimits[2] + heighLimits[3]) / 2, "max" : (heighLimits[4] + heighLimits[5]) / 2 };
459 let playerHeight = (playerHeightRange.min + playerHeightRange.max) / 2; // Average player height
461 log("Terrain shape generation and biome presets after " + ((Date.now() - genStartTime) / 1000) + "s");
464 * Get start locations
466 let startLocations = getStartLocationsByHeightmap(playerHeightRange, 1000, 30);
468 // Sort start locations to form a "ring"
469 let startLocationOrder = getOrderOfPointsForShortestClosePath(startLocations);
470 let newStartLocations = [];
471 for (let i = 0; i < startLocations.length; ++i)
472 newStartLocations.push(startLocations[startLocationOrder[i]]);
473 startLocations = newStartLocations;
475 // Sort players by team
478 for (let i = 0; i < g_MapSettings.PlayerData.length - 1; ++i)
481 let t = g_MapSettings.PlayerData[i + 1].Team;
482 if (teams.indexOf(t) == -1 && t !== undefined)
485 playerIDs = sortPlayers(playerIDs);
487 // Minimize maximum distance between players within a team
490 let minDistance = Infinity;
492 for (let s = 0; s < playerIDs.length; ++s)
495 for (let pi = 0; pi < playerIDs.length - 1; ++pi)
497 let p1 = playerIDs[(pi + s) % playerIDs.length] - 1;
498 let t1 = getPlayerTeam(p1);
500 if (teams.indexOf(t1) === -1)
503 for (let pj = pi + 1; pj < playerIDs.length; ++pj)
505 let p2 = playerIDs[(pj + s) % playerIDs.length] - 1;
506 let t2 = getPlayerTeam(p2);
510 maxTeamDist = Math.max(
512 Math.euclidDistance2D(
513 startLocations[pi].x, startLocations[pi].y,
514 startLocations[pj].x, startLocations[pj].y));
518 if (maxTeamDist < minDistance)
520 minDistance = maxTeamDist;
526 let newPlayerIDs = [];
527 for (let i = 0; i < playerIDs.length; ++i)
528 newPlayerIDs.push(playerIDs[(i + bestShift) % playerIDs.length]);
529 playerIDs = newPlayerIDs;
533 log("Start location chosen after " + ((Date.now() - genStartTime) / 1000) + "s");
537 * Smooth Start Locations before height region calculation
539 let playerBaseRadius = 35;
540 if (g_Map.size < 256)
541 playerBaseRadius = 25;
542 for (let p = 0; p < playerIDs.length; ++p)
543 rectangularSmoothToHeight(startLocations[p], playerBaseRadius, playerBaseRadius, playerHeight, 0.7);
546 * Calculate tile centered height map after start position smoothing but before placing paths
547 * This has nothing to to with TILE_CENTERED_HEIGHT_MAP which should be false!
549 let tchm = getTileCenteredHeightmap();
554 let clPath = createTileClass();
557 * Divide tiles in areas by height and avoid paths
560 for (let h = 0; h < heighLimits.length; ++h)
563 for (let x = 0; x < tchm.length; ++x)
565 for (let y = 0; y < tchm[0].length; ++y)
567 if (g_Map.tileClasses[clPath].inclusionCount[x][y] > 0) // Avoid paths
570 let minHeight = heightRange.min;
571 for (let h = 0; h < heighLimits.length; ++h)
573 if (tchm[x][y] >= minHeight && tchm[x][y] <= heighLimits[h])
575 areas[h].push({ "x": x, "y": y });
579 minHeight = heighLimits[h];
585 * Get max slope of each area
587 let slopeMap = getSlopeMap();
590 for (let h = 0; h < heighLimits.length; ++h)
592 minSlope[h] = Infinity;
594 for (let t = 0; t < areas[h].length; ++t)
596 let x = areas[h][t].x;
597 let y = areas[h][t].y;
598 let slope = slopeMap[x][y];
600 if (slope > maxSlope[h])
603 if (slope < minSlope[h])
609 * Paint areas by height and slope
611 for (let h = 0; h < heighLimits.length; ++h)
613 for (let t = 0; t < areas[h].length; ++t)
615 let x = areas[h][t].x;
616 let y = areas[h][t].y;
618 let texture = pickRandom(wildLakeBiome[h].texture);
620 if (slopeMap[x][y] < 0.5 * (minSlope[h] + maxSlope[h]))
622 if (randBool(wildLakeBiome[h].actor[1]))
623 actor = pickRandom(wildLakeBiome[h].actor[0]);
627 texture = pickRandom(wildLakeBiome[h].textureHS);
628 if (randBool(wildLakeBiome[h].actorHS[1]))
629 actor = pickRandom(wildLakeBiome[h].actorHS[0]);
632 g_Map.texture[x][y] = g_Map.getTextureID(texture);
635 placeObject(randFloat(x, x + 1), randFloat(y, y + 1), actor, 0, randFloat(0, 2 * PI));
639 log("Terrain texture placement finished after " + ((Date.now() - genStartTime) / 1000) + "s");
643 * Get resource spots after players start locations calculation and paths
645 let avoidPoints = clone(startLocations);
646 for (let i = 0; i < avoidPoints.length; ++i)
647 avoidPoints[i].dist = 30;
648 let resourceSpots = getPointsByHeight(resourceSpotHeightRange, avoidPoints, clPath);
650 log("Resource spots chosen after " + ((Date.now() - genStartTime) / 1000) + "s");
654 * Add start locations and resource spots after terrain texture and path painting
656 for (let p = 0; p < playerIDs.length; ++p)
658 let point = startLocations[p];
659 placeCivDefaultEntities(point.x, point.y, playerIDs[p], { "iberWall": g_Map.size > 192 });
660 placeStartLocationResources(point);
663 let mercenaryCamps = ceil(g_Map.size / 256);
664 log("Maximum number of mercenary camps: " + uneval(mercenaryCamps));
665 for (let i = 0; i < resourceSpots.length; ++i)
669 placeMine(resourceSpots[i], g_Gaia.stoneLarge);
671 placeMine(resourceSpots[i], g_Gaia.metalLarge);
673 placeGrove(resourceSpots[i]);
676 placeCamp(resourceSpots[i]);
677 rectangularSmoothToHeight(resourceSpots[i], 5, 5, g_Map.height[resourceSpots[i].x][resourceSpots[i].y] - 10, 0.5);
683 createStartingPlayerEntities(resourceSpots[i].x, resourceSpots[i].y, 0, mercenaryCampGuards[currentBiome()]);
684 rectangularSmoothToHeight(resourceSpots[i], 15, 15, g_Map.height[resourceSpots[i].x][resourceSpots[i].y], 0.5);
689 placeCustomFortress(resourceSpots[i].x, resourceSpots[i].y, pickRandom(fences), "other", 0, randFloat(0, 2 * PI));
690 rectangularSmoothToHeight(resourceSpots[i], 10, 10, g_Map.height[resourceSpots[i].x][resourceSpots[i].y], 0.5);
695 log("Map generation finished after " + ((Date.now() - genStartTime) / 1000) + "s");