1 Engine.LoadLibrary("rmgen");
2 Engine.LoadLibrary("rmbiome");
3 Engine.LoadLibrary("heightmap");
8 * getArray - To ensure a terrain texture is contained within an array
10 function getArray(stringOrArrayOfStrings)
12 if (typeof stringOrArrayOfStrings == "string")
13 return [stringOrArrayOfStrings];
14 return stringOrArrayOfStrings;
19 // Terrain, entities and actors
23 "texture": getArray(g_Terrains.water),
24 "actor": [[g_Gaia.fish], 0.01],
25 "textureHS": getArray(g_Terrains.water),
26 "actorHS": [[g_Gaia.fish], 0.03]
30 "texture": getArray(g_Terrains.water),
31 "actor": [[g_Decoratives.lillies, g_Decoratives.reeds], 0.3],
32 "textureHS": getArray(g_Terrains.water),
33 "actorHS": [[g_Decoratives.lillies], 0.1]
37 "texture": getArray(g_Terrains.shore),
40 g_Gaia.tree1, g_Gaia.tree1,
41 g_Gaia.tree2, g_Gaia.tree2,
42 g_Gaia.mainHuntableAnimal,
43 g_Decoratives.grass, g_Decoratives.grass,
44 g_Decoratives.rockMedium, g_Decoratives.rockMedium,
45 g_Decoratives.bushMedium, g_Decoratives.bushMedium
49 "textureHS": getArray(g_Terrains.cliff),
50 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
54 "texture": getArray(g_Terrains.tier1Terrain),
58 g_Decoratives.grassShort,
59 g_Decoratives.rockLarge,
60 g_Decoratives.rockMedium,
61 g_Decoratives.bushMedium,
62 g_Decoratives.bushSmall
66 "textureHS": getArray(g_Terrains.cliff),
67 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
69 // 4 Mid ground. Player and path height
71 "texture": getArray(g_Terrains.mainTerrain),
75 g_Decoratives.grassShort,
76 g_Decoratives.rockLarge,
77 g_Decoratives.rockMedium,
78 g_Decoratives.bushMedium,
79 g_Decoratives.bushSmall
83 "textureHS": getArray(g_Terrains.cliff),
84 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
88 "texture": getArray(g_Terrains.tier2Terrain),
92 g_Decoratives.grassShort,
93 g_Decoratives.rockLarge,
94 g_Decoratives.rockMedium,
95 g_Decoratives.bushMedium,
96 g_Decoratives.bushSmall
100 "textureHS": getArray(g_Terrains.cliff),
101 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
103 // 6 Lower hilltop forest border
105 "texture": getArray(g_Terrains.dirt),
111 g_Gaia.secondaryHuntableAnimal,
113 g_Decoratives.rockMedium,
114 g_Decoratives.bushMedium
118 "textureHS": getArray(g_Terrains.cliff),
119 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
123 "texture": getArray(g_Terrains.forestFloor1),
133 g_Decoratives.rockMedium,
134 g_Decoratives.bushMedium
138 "textureHS": getArray(g_Terrains.cliff),
139 "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1]
143 var mercenaryCampGuards = {
145 { "Template" : "structures/merc_camp_egyptian" },
146 { "Template" : "units/mace_infantry_javelinist_b", "Count" : 4 },
147 { "Template" : "units/mace_cavalry_spearman_e", "Count" : 3 },
148 { "Template" : "units/mace_infantry_archer_a", "Count" : 4 },
149 { "Template" : "units/mace_champion_infantry_a", "Count" : 3 }
152 { "Template" : "structures/ptol_mercenary_camp" },
153 { "Template" : "units/brit_infantry_javelinist_b", "Count" : 4 },
154 { "Template" : "units/brit_cavalry_swordsman_e", "Count" : 3 },
155 { "Template" : "units/brit_infantry_slinger_a", "Count" : 4 },
156 { "Template" : "units/brit_champion_infantry", "Count" : 3 }
159 { "Template" : "structures/ptol_mercenary_camp" },
160 { "Template" : "units/pers_infantry_javelinist_b", "Count" : 4 },
161 { "Template" : "units/pers_cavalry_swordsman_e", "Count" : 3 },
162 { "Template" : "units/pers_infantry_archer_a", "Count" : 4 },
163 { "Template" : "units/pers_champion_infantry", "Count" : 3 }
166 { "Template" : "structures/ptol_mercenary_camp" },
167 { "Template" : "units/rome_infantry_swordsman_b", "Count" : 4 },
168 { "Template" : "units/rome_cavalry_spearman_e", "Count" : 3 },
169 { "Template" : "units/rome_infantry_javelinist_a", "Count" : 4 },
170 { "Template" : "units/rome_champion_infantry", "Count" : 3 }
173 { "Template" : "structures/merc_camp_egyptian" },
174 { "Template" : "units/iber_infantry_javelinist_b", "Count" : 4 },
175 { "Template" : "units/iber_cavalry_spearman_e", "Count" : 3 },
176 { "Template" : "units/iber_infantry_slinger_a", "Count" : 4 },
177 { "Template" : "units/iber_champion_infantry", "Count" : 3 }
180 { "Template" : "structures/merc_camp_egyptian" },
181 { "Template" : "units/sele_infantry_javelinist_b", "Count" : 4 },
182 { "Template" : "units/sele_cavalry_spearman_merc_e", "Count" : 3 },
183 { "Template" : "units/sele_infantry_spearman_a", "Count" : 4 },
184 { "Template" : "units/sele_champion_infantry_swordsman", "Count" : 3 }
187 { "Template" : "structures/merc_camp_egyptian" },
188 { "Template" : "units/ptol_infantry_javelinist_b", "Count" : 4 },
189 { "Template" : "units/ptol_cavalry_archer_e", "Count" : 3 },
190 { "Template" : "units/ptol_infantry_slinger_a", "Count" : 4 },
191 { "Template" : "units/ptol_champion_infantry_pikeman", "Count" : 3 }
194 { "Template" : "structures/ptol_mercenary_camp" },
195 { "Template" : "units/gaul_infantry_javelinist_b", "Count" : 4 },
196 { "Template" : "units/gaul_cavalry_swordsman_e", "Count" : 3 },
197 { "Template" : "units/gaul_infantry_slinger_a", "Count" : 4 },
198 { "Template" : "units/gaul_champion_infantry", "Count" : 3 }
203 * Resource spots and other points of interest
207 function placeMine(point, centerEntity,
209 g_Decoratives.grass, g_Decoratives.grassShort,
210 g_Decoratives.rockLarge, g_Decoratives.rockMedium,
211 g_Decoratives.bushMedium, g_Decoratives.bushSmall
215 placeObject(point.x, point.y, centerEntity, 0, randFloat(0, 2 * Math.PI));
216 let quantity = randIntInclusive(11, 23);
217 let dAngle = 2 * Math.PI / quantity;
218 for (let i = 0; i < quantity; ++i)
220 let angle = dAngle * randFloat(i, i + 1);
221 let dist = randFloat(2, 5);
222 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(decorativeActors), 0, randFloat(0, 2 * Math.PI));
227 let groveActors = [g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium];
228 let clGrove = createTileClass();
230 function placeGrove(point,
232 g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1,
233 g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2,
234 g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree3,
235 g_Gaia.tree4, g_Gaia.tree4, g_Gaia.tree5
237 groveActors = [g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium], groveTileClass = undefined,
238 groveTerrainTexture = getArray(g_Terrains.forestFloor1)
241 placeObject(point.x, point.y, pickRandom(["structures/gaul_outpost", "gaia/flora_tree_oak_new"]), 0, randFloat(0, 2 * Math.PI));
242 let quantity = randIntInclusive(20, 30);
243 let dAngle = 2 * Math.PI / quantity;
244 for (let i = 0; i < quantity; ++i)
246 let angle = dAngle * randFloat(i, i + 1);
247 let dist = randFloat(2, 5);
248 let objectList = groveEntities;
250 objectList = groveActors;
251 let x = point.x + dist * Math.cos(angle);
252 let y = point.y + dist * Math.sin(angle);
253 placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * Math.PI));
255 createArea(new ClumpPlacer(5, 1, 1, 1, Math.floor(x), Math.floor(y)), [new TerrainPainter(groveTerrainTexture), paintClass(groveTileClass)]);
257 createArea(new ClumpPlacer(5, 1, 1, 1, Math.floor(x), Math.floor(y)), [new TerrainPainter(groveTerrainTexture)]);
262 "temperate": { "building": "structures/mace_farmstead", "animal": "gaia/fauna_pig" },
263 "snowy": { "building": "structures/brit_farmstead", "animal": "gaia/fauna_sheep" },
264 "desert": { "building": "structures/pers_farmstead", "animal": "gaia/fauna_camel" },
265 "alpine": { "building": "structures/rome_farmstead", "animal": "gaia/fauna_sheep" },
266 "mediterranean": { "building": "structures/iber_farmstead", "animal": "gaia/fauna_pig" },
267 "savanna": { "building": "structures/sele_farmstead", "animal": "gaia/fauna_horse" },
268 "tropic": { "building": "structures/ptol_farmstead", "animal": "gaia/fauna_camel" },
269 "autumn": { "building": "structures/gaul_farmstead", "animal": "gaia/fauna_horse" }
272 g_WallStyles.other = {
274 "fence": readyWallElement("other/fence_long", "gaia"),
275 "fence_short": readyWallElement("other/fence_short", "gaia"),
276 "bench": { "angle": Math.PI / 2, "length": 1.5, "indent": 0, "bend": 0, "templateName": "other/bench" },
277 "foodBin": { "angle": Math.PI / 2, "length": 1.5, "indent": 0, "bend": 0, "templateName": "gaia/special_treasure_food_bin" },
278 "animal": { "angle": 0, "length": 0, "indent": 0.75, "bend": 0, "templateName": farmEntities[currentBiome()].animal },
279 "farmstead": { "angle": Math.PI, "length": 0, "indent": -3, "bend": 0, "templateName": farmEntities[currentBiome()].building }
283 new Fortress("fence", [
284 "foodBin", "farmstead", "bench",
285 "turn_0.25", "animal", "turn_0.25", "fence",
286 "turn_0.25", "animal", "turn_0.25", "fence",
287 "turn_0.25", "animal", "turn_0.25", "fence"
289 new Fortress("fence", [
290 "foodBin", "farmstead", "fence",
291 "turn_0.25", "animal", "turn_0.25", "fence",
292 "turn_0.25", "animal", "turn_0.25", "bench", "animal", "fence",
293 "turn_0.25", "animal", "turn_0.25", "fence"
295 new Fortress("fence", [
296 "foodBin", "farmstead", "turn_0.5", "bench", "turn_-0.5", "fence_short",
297 "turn_0.25", "animal", "turn_0.25", "fence",
298 "turn_0.25", "animal", "turn_0.25", "fence",
299 "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence"
301 new Fortress("fence", [
302 "foodBin", "farmstead", "turn_0.5", "fence_short", "turn_-0.5", "bench",
303 "turn_0.25", "animal", "turn_0.25", "fence",
304 "turn_0.25", "animal", "turn_0.25", "fence",
305 "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence"
307 new Fortress("fence", [
308 "foodBin", "farmstead", "fence",
309 "turn_0.25", "animal", "turn_0.25", "bench", "animal", "fence",
310 "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence",
311 "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence"
314 let num = fences.length;
315 for (let i = 0; i < num; ++i)
316 fences.push(new Fortress("fence", clone(fences[i].wall).reverse()));
318 // Camps with fire and gold treasure
319 function placeCamp(point,
320 centerEntity = "actor|props/special/eyecandy/campfire.xml",
321 otherEntities = ["gaia/special_treasure_metal", "gaia/special_treasure_standing_stone",
322 "units/brit_infantry_slinger_b", "units/brit_infantry_javelinist_b", "units/gaul_infantry_slinger_b", "units/gaul_infantry_javelinist_b", "units/gaul_champion_fanatic",
323 "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"
327 placeObject(point.x, point.y, centerEntity, 0, randFloat(0, 2 * Math.PI));
328 let quantity = randIntInclusive(5, 11);
329 let dAngle = 2 * Math.PI / quantity;
330 for (let i = 0; i < quantity; ++i)
332 let angle = dAngle * randFloat(i, i + 1);
333 let dist = randFloat(1, 3);
334 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(otherEntities), 0, randFloat(0, 2 * Math.PI));
338 function placeStartLocationResources(
340 foodEntities = [g_Gaia.fruitBush, g_Gaia.chicken],
342 g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1,
343 g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2,
344 g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree3,
345 g_Gaia.tree4, g_Gaia.tree4, g_Gaia.tree5
347 groveTerrainTexture = getArray(g_Terrains.forestFloor1),
348 averageDistToCC = 10,
352 function getRandDist()
354 return averageDistToCC + randFloat(-dAverageDistToCC, dAverageDistToCC);
357 let currentAngle = randFloat(0, 2 * Math.PI);
359 let dAngle = 4/9 * Math.PI;
360 let angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
361 placeMine({ "x": point.x + averageDistToCC * Math.cos(angle), "y": point.y + averageDistToCC * Math.sin(angle) }, g_Gaia.stoneLarge);
363 currentAngle += dAngle;
367 dAngle = 2/3 * Math.PI / quantity;
368 for (let i = 0; i < quantity; ++i)
370 angle = currentAngle + randFloat(0, dAngle);
371 let dist = getRandDist();
372 let objectList = groveEntities;
374 objectList = groveActors;
375 let x = point.x + dist * Math.cos(angle);
376 let y = point.y + dist * Math.sin(angle);
377 placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * Math.PI));
378 createArea(new ClumpPlacer(5, 1, 1, 1, Math.floor(x), Math.floor(y)), [new TerrainPainter(groveTerrainTexture), paintClass(clGrove)]);
379 currentAngle += dAngle;
383 dAngle = 4/9 * Math.PI;
384 angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
385 placeMine({ "x": point.x + averageDistToCC * Math.cos(angle), "y": point.y + averageDistToCC * Math.sin(angle) }, g_Gaia.metalLarge);
386 currentAngle += dAngle;
388 // Berries and domestic animals
390 dAngle = 4/9 * Math.PI / quantity;
391 for (let i = 0; i < quantity; ++i)
393 angle = currentAngle + randFloat(0, dAngle);
394 let dist = getRandDist();
395 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(foodEntities), 0, randFloat(0, 2 * Math.PI));
396 currentAngle += dAngle;
401 * Base terrain shape generation and settings
403 // Height range by map size
404 let heightScale = (g_Map.size + 256) / 768 / 4;
405 let heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale };
408 let averageWaterCoverage = 1/5; // NOTE: Since terrain generation is quite unpredictable actual water coverage might vary much with the same value
409 let waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); // Water height in environment and the engine
410 let waterHeightAdjusted = waterHeight + MIN_HEIGHT; // Water height as terrain height
411 setWaterHeight(waterHeight);
413 // Generate base terrain shape
414 let lowH = heightRange.min;
415 let medH = (heightRange.min + heightRange.max) / 2;
418 let initialHeightmap = [
419 [medH, medH, medH, medH, medH, medH],
420 [medH, medH, medH, medH, medH, medH],
421 [medH, medH, lowH, lowH, medH, medH],
422 [medH, medH, lowH, lowH, medH, medH],
423 [medH, medH, medH, medH, medH, medH],
424 [medH, medH, medH, medH, medH, medH],
426 if (g_Map.size < 256)
429 [medH, medH, medH, medH, medH],
430 [medH, medH, medH, medH, medH],
431 [medH, medH, lowH, medH, medH],
432 [medH, medH, medH, medH, medH],
433 [medH, medH, medH, medH, medH]
436 if (g_Map.size >= 384)
439 [medH, medH, medH, medH, medH, medH, medH, medH],
440 [medH, medH, medH, medH, medH, medH, medH, medH],
441 [medH, medH, medH, medH, medH, medH, medH, medH],
442 [medH, medH, medH, lowH, lowH, medH, medH, medH],
443 [medH, medH, medH, lowH, lowH, medH, medH, medH],
444 [medH, medH, medH, medH, medH, medH, medH, medH],
445 [medH, medH, medH, medH, medH, medH, medH, medH],
446 [medH, medH, medH, medH, medH, medH, medH, medH],
450 setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8);
452 // Apply simple erosion
453 for (let i = 0; i < 5; ++i)
455 globalSmoothHeightmap();
458 rescaleHeightmap(heightRange.min, heightRange.max);
460 Engine.SetProgress(25);
463 * Prepare terrain texture placement
466 heightRange.min + 3/4 * (waterHeightAdjusted - heightRange.min), // 0 Deep water
467 waterHeightAdjusted, // 1 Shallow water
468 waterHeightAdjusted + 2/8 * (heightRange.max - waterHeightAdjusted), // 2 Shore
469 waterHeightAdjusted + 3/8 * (heightRange.max - waterHeightAdjusted), // 3 Low ground
470 waterHeightAdjusted + 4/8 * (heightRange.max - waterHeightAdjusted), // 4 Player and path height
471 waterHeightAdjusted + 6/8 * (heightRange.max - waterHeightAdjusted), // 5 High ground
472 waterHeightAdjusted + 7/8 * (heightRange.max - waterHeightAdjusted), // 6 Lower forest border
473 heightRange.max // 7 Forest
475 let playerHeightRange = { "min" : heighLimits[3], "max" : heighLimits[4] };
476 let resourceSpotHeightRange = { "min" : (heighLimits[2] + heighLimits[3]) / 2, "max" : (heighLimits[4] + heighLimits[5]) / 2 };
477 let playerHeight = (playerHeightRange.min + playerHeightRange.max) / 2; // Average player height
479 log("Chosing starting locations...");
480 let [playerIDs, startLocations] = sortPlayersByLocation(getStartLocationsByHeightmap(playerHeightRange, 1000, 30));
482 Engine.SetProgress(30);
485 * Smooth Start Locations before height region calculation
487 let playerBaseRadius = 35;
488 if (g_Map.size < 256)
489 playerBaseRadius = 25;
490 for (let p = 0; p < playerIDs.length; ++p)
491 rectangularSmoothToHeight(startLocations[p], playerBaseRadius, playerBaseRadius, playerHeight, 0.7);
494 * Calculate tile centered height map after start position smoothing but before placing paths
495 * This has nothing to to with TILE_CENTERED_HEIGHT_MAP which should be false!
497 let tchm = getTileCenteredHeightmap();
502 let clPath = createTileClass();
505 * Divide tiles in areas by height and avoid paths
508 for (let h = 0; h < heighLimits.length; ++h)
511 for (let x = 0; x < tchm.length; ++x)
512 for (let y = 0; y < tchm[0].length; ++y)
514 if (g_Map.tileClasses[clPath].inclusionCount[x][y] > 0) // Avoid paths
517 let minHeight = heightRange.min;
518 for (let h = 0; h < heighLimits.length; ++h)
520 if (tchm[x][y] >= minHeight && tchm[x][y] <= heighLimits[h])
522 areas[h].push({ "x": x, "y": y });
526 minHeight = heighLimits[h];
531 * Get max slope of each area
533 let slopeMap = getSlopeMap();
536 for (let h = 0; h < heighLimits.length; ++h)
538 minSlope[h] = Infinity;
540 for (let t = 0; t < areas[h].length; ++t)
542 let x = areas[h][t].x;
543 let y = areas[h][t].y;
544 let slope = slopeMap[x][y];
546 if (slope > maxSlope[h])
549 if (slope < minSlope[h])
555 * Paint areas by height and slope
557 for (let h = 0; h < heighLimits.length; ++h)
558 for (let t = 0; t < areas[h].length; ++t)
560 let x = areas[h][t].x;
561 let y = areas[h][t].y;
563 let texture = pickRandom(wildLakeBiome[h].texture);
565 if (slopeMap[x][y] < 0.5 * (minSlope[h] + maxSlope[h]))
567 if (randBool(wildLakeBiome[h].actor[1]))
568 actor = pickRandom(wildLakeBiome[h].actor[0]);
572 texture = pickRandom(wildLakeBiome[h].textureHS);
573 if (randBool(wildLakeBiome[h].actorHS[1]))
574 actor = pickRandom(wildLakeBiome[h].actorHS[0]);
577 g_Map.texture[x][y] = g_Map.getTextureID(texture);
580 placeObject(randFloat(x, x + 1), randFloat(y, y + 1), actor, 0, randFloat(0, 2 * Math.PI));
582 Engine.SetProgress(80);
584 log("Placing resources...");
585 let avoidPoints = clone(startLocations);
586 for (let i = 0; i < avoidPoints.length; ++i)
587 avoidPoints[i].dist = 30;
588 let resourceSpots = getPointsByHeight(resourceSpotHeightRange, avoidPoints, clPath);
589 Engine.SetProgress(55);
591 log("Placing players...");
593 placePlayersNomad(createTileClass(), new HeightConstraint(playerHeightRange.min, playerHeightRange.max));
595 for (let p = 0; p < playerIDs.length; ++p)
597 let point = startLocations[p];
598 placeCivDefaultStartingEntities(point.x, point.y, playerIDs[p], g_Map.size > 192);
599 placeStartLocationResources(point);
602 let mercenaryCamps = isNomad() ? 0 : Math.ceil(g_Map.size / 256);
603 log("Maximum number of mercenary camps: " + mercenaryCamps);
604 for (let i = 0; i < resourceSpots.length; ++i)
608 placeMine(resourceSpots[i], g_Gaia.stoneLarge);
610 placeMine(resourceSpots[i], g_Gaia.metalLarge);
612 placeGrove(resourceSpots[i]);
615 placeCamp(resourceSpots[i]);
616 rectangularSmoothToHeight(resourceSpots[i], 5, 5, g_Map.height[resourceSpots[i].x][resourceSpots[i].y] - 10, 0.5);
622 placeStartingEntities(resourceSpots[i].x, resourceSpots[i].y, 0, mercenaryCampGuards[currentBiome()]);
623 rectangularSmoothToHeight(resourceSpots[i], 15, 15, g_Map.height[resourceSpots[i].x][resourceSpots[i].y], 0.5);
628 placeCustomFortress(resourceSpots[i].x, resourceSpots[i].y, pickRandom(fences), "other", 0, randFloat(0, 2 * Math.PI));
629 rectangularSmoothToHeight(resourceSpots[i], 10, 10, g_Map.height[resourceSpots[i].x][resourceSpots[i].y], 0.5);