0ad/binaries/data/mods/public/maps/random/unknown.js
Dunedan 93ce94655d
Use @stylistic/brace-style for eslint
Up to now `eslint-plugin-brace-rules` was used to enforce a common brace
style for JavaScript code. This plugin was however updated the last time
over 9 years ago and will be incompatible with ESLint v10, as that
[removes `context.getSourceCode()`][1], the plugin relies on.

To keep the eslint config working with ESLint v10, this replaces
`eslint-plugin-brace-rules` with the [`@stylistic/brace-style`][2] rule
from `@stylistic/eslint-plugin`, a package we already use.

While `@stylistic/brace-style` doesn't offer an option to format braces
in exactly the same way as before, the "allman" style seems to be the
one closest to the existing code.

[1]: https://eslint.org/blog/2025/11/eslint-v10.0.0-alpha.0-released/#removed-deprecated-rule-context-members
[2]: https://eslint.style/rules/brace-style
2026-01-12 21:33:52 +01:00

1133 lines
32 KiB
JavaScript

Engine.LoadLibrary("rmgen");
Engine.LoadLibrary("rmgen-common");
Engine.LoadLibrary("rmbiome");
export function* generateMap(mapSettings)
{
TILE_CENTERED_HEIGHT_MAP = true;
setBiome(mapSettings.Biome);
const tMainTerrain = g_Terrains.mainTerrain;
const tForestFloor1 = g_Terrains.forestFloor1;
const tForestFloor2 = g_Terrains.forestFloor2;
const tCliff = g_Terrains.cliff;
const tTier1Terrain = g_Terrains.tier1Terrain;
const tTier2Terrain = g_Terrains.tier2Terrain;
const tTier3Terrain = g_Terrains.tier3Terrain;
const tHill = g_Terrains.hill;
const tRoad = g_Terrains.road;
const tRoadWild = g_Terrains.roadWild;
const tTier4Terrain = g_Terrains.tier4Terrain;
const tShore = g_Terrains.shore;
const tWater = g_Terrains.water;
const oTree1 = g_Gaia.tree1;
const oTree2 = g_Gaia.tree2;
const oTree4 = g_Gaia.tree4;
const oTree5 = g_Gaia.tree5;
const oFruitBush = g_Gaia.fruitBush;
const oMainHuntableAnimal = g_Gaia.mainHuntableAnimal;
const oSecondaryHuntableAnimal = g_Gaia.secondaryHuntableAnimal;
const oFish = g_Gaia.fish;
const oStoneLarge = g_Gaia.stoneLarge;
const oStoneSmall = g_Gaia.stoneSmall;
const oMetalLarge = g_Gaia.metalLarge;
const oWoodTreasure = "gaia/treasure/wood";
const aGrass = g_Decoratives.grass;
const aGrassShort = g_Decoratives.grassShort;
const aReeds = g_Decoratives.reeds;
const aLillies = g_Decoratives.lillies;
const aRockLarge = g_Decoratives.rockLarge;
const aRockMedium = g_Decoratives.rockMedium;
const aBushMedium = g_Decoratives.bushMedium;
const aBushSmall = g_Decoratives.bushSmall;
const pForest1 = [
tForestFloor2 + TERRAIN_SEPARATOR + oTree1,
tForestFloor2 + TERRAIN_SEPARATOR + oTree2,
tForestFloor2
];
const pForest2 = [
tForestFloor1 + TERRAIN_SEPARATOR + oTree4,
tForestFloor1 + TERRAIN_SEPARATOR + oTree5,
tForestFloor1
];
const heightSeaGround = -5;
const heightLand = 3;
const heightCliff = 3.12;
const heightHill = 18;
const heightOffsetBump = 2;
globalThis.g_Map = new RandomMap(heightSeaGround, tWater);
const numPlayers = getNumPlayers();
const mapSize = g_Map.getSize();
const mapCenter = g_Map.getCenter();
const mapBounds = g_Map.getBounds();
const clPlayer = g_Map.createTileClass();
const clPlayerTerritory = g_Map.createTileClass();
const clHill = g_Map.createTileClass();
const clForest = g_Map.createTileClass();
const clWater = g_Map.createTileClass();
const clDirt = g_Map.createTileClass();
const clRock = g_Map.createTileClass();
const clMetal = g_Map.createTileClass();
const clFood = g_Map.createTileClass();
const clPeninsulaSteam = g_Map.createTileClass();
const clBaseResource = g_Map.createTileClass();
const clLand = g_Map.createTileClass();
const clShallow = g_Map.createTileClass();
const landElevationPainter = new SmoothElevationPainter(ELEVATION_SET, heightLand, 4);
/**
* The player IDs and locations shall only be determined by the landscape
* functions if it's not a nomad game, because nomad maps randomize the
* locations after the terrain generation.
* The locations should only determined by the landscape functions to
* avoid placing bodies of water and resources into civic centers and the
* starting resources.
*/
let playerIDs = sortAllPlayers();
let playerPosition = [];
let g_StartingTreasures = false;
let g_StartingWalls = true;
/**
* Creates a huge central river, possibly connecting the riversides with a
* narrow piece of land.
*/
function unknownCentralSeaOrIsthmus(isthmus)
{
const waterHeight = -3;
const startAngle = randomAngle();
const [riverStart, riverEnd] = centralRiverCoordinates(startAngle);
paintRiver({
"parallel": false,
"start": riverStart,
"end": riverEnd,
"width": fractionToTiles(scaleByMapSize(0.27, 0.42) + randFloat(0, 0.08)),
"fadeDist": scaleByMapSize(3, 12),
"deviation": 0,
"heightRiverbed": waterHeight,
"heightLand": heightLand,
"meanderShort": 20,
"meanderLong": 0,
"waterFunc": (position, height, riverFraction) =>
{
if (height < 0)
clWater.add(position);
},
"landFunc": (position, shoreDist1, shoreDist2) =>
{
g_Map.setHeight(position, 3.1);
clLand.add(position);
}
});
if (!mapSettings.Nomad)
{
({ playerIDs, playerPosition } =
playerPlacementRiver(startAngle + Math.PI / 2, fractionToTiles(0.6)));
markPlayerArea("small");
}
if (isthmus)
{
g_Map.log(
"Creating isthmus (i.e. connecting the two riversides with a big land passage)");
const [isthmusStart, isthmusEnd] = centralRiverCoordinates(startAngle + Math.PI / 2);
createArea(
new PathPlacer(
isthmusStart,
isthmusEnd,
scaleByMapSize(randIntInclusive(16, 24), randIntInclusive(100, 140)),
0.5,
3 * scaleByMapSize(1, 4),
0.1,
0.01),
[
landElevationPainter,
new TileClassPainter(clLand),
new TileClassUnPainter(clWater)
]);
}
createExtensionsOrIslands();
// Don't createShoreJaggedness since it doesn't fit artistically
// here
}
/**
* Creates a very small central river.
*/
function unknownCentralRiver(shallows)
{
const waterHeight = -4;
const heightShallow = -2;
createArea(
new MapBoundsPlacer(),
new ElevationPainter(heightLand));
const startAngle = randomAngle();
if (!mapSettings.Nomad)
{
({ playerIDs, playerPosition } =
playerPlacementRiver(startAngle + Math.PI / 2, fractionToTiles(0.5)));
markPlayerArea("large");
}
g_Map.log("Creating the main river");
const [coord1, coord2] = centralRiverCoordinates(startAngle);
createArea(
new PathPlacer(coord1, coord2, scaleByMapSize(14, 24), 0.5, scaleByMapSize(3, 12), 0.1,
0.01),
new SmoothElevationPainter(ELEVATION_SET, waterHeight, 4),
avoidClasses(clPlayerTerritory, 4));
g_Map.log("Creating small water spots at the map border to ensure separation of players");
for (const coord of [coord1, coord2])
createArea(
new ClumpPlacer(diskArea(scaleByMapSize(5, 10)), 0.95, 0.6, Infinity, coord),
new SmoothElevationPainter(ELEVATION_SET, waterHeight, 2),
avoidClasses(clPlayerTerritory, 8));
if (shallows)
{
g_Map.log("Creating the shallows of the main river");
for (let i = 0; i <= randIntInclusive(1, scaleByMapSize(4, 8)); ++i)
{
const location = fractionToTiles(randFloat(0.15, 0.85));
createPassage({
"start": new Vector2D(location, mapBounds.top)
.rotateAround(startAngle, mapCenter),
"end": new Vector2D(location, mapBounds.bottom)
.rotateAround(startAngle, mapCenter),
"startWidth": scaleByMapSize(8, 12),
"endWidth": scaleByMapSize(8, 12),
"smoothWidth": 2,
"startHeight": heightShallow,
"endHeight": heightShallow,
"constraints": new HeightConstraint(-Infinity, heightShallow),
"tileClass": clShallow
});
}
}
if (randBool(2/3))
createTributaryRivers(
startAngle,
randIntInclusive(8, scaleByMapSize(12, 16)),
scaleByMapSize(10, 20),
-4,
[-6, -1.5],
Math.PI / 5,
clWater,
clShallow,
avoidClasses(clPlayerTerritory, 3));
}
const unknownMapFunctions = {
// Chain of islands or many disconnected islands.
"Archipelago": () =>
{
g_StartingWalls = "towers";
g_StartingTreasures = true;
const { "playerIDs": pIDs, "playerPosition": islandPosition } =
playerPlacementCircle(fractionToTiles(0.35));
if (!mapSettings.Nomad)
{
[playerIDs, playerPosition] = [pIDs, islandPosition];
markPlayerArea("large");
}
g_Map.log("Creating islands");
const islandSize = diskArea(scaleByMapSize(17, 29));
for (let i = 0; i < numPlayers; ++i)
createArea(
new ClumpPlacer(islandSize, 0.8, 0.1, Infinity, islandPosition[i]),
landElevationPainter);
switch (randIntInclusive(1, mapSettings.Nomad ? 2 : 3))
{
case 1:
g_Map.log("Creating archipelago");
createAreas(
new ClumpPlacer(islandSize * randFloat(0.8, 1.2), 0.8, 0.1, Infinity),
[
landElevationPainter,
new TileClassPainter(clLand)
],
null,
scaleByMapSize(2, 5) * randIntInclusive(8, 14));
g_Map.log("Creating shore jaggedness with small puddles");
createAreas(
new ClumpPlacer(scaleByMapSize(15, 80), 0.2, 0.1, Infinity),
[
new SmoothElevationPainter(ELEVATION_SET, heightLand, 4),
new TileClassPainter(clLand)
],
borderClasses(clLand, 6, 3),
scaleByMapSize(12, 130) * 2,
150);
break;
case 2:
g_Map.log("Creating islands");
createAreas(
new ClumpPlacer(islandSize * randFloat(0.6, 1.4), 0.8, 0.1,
randFloat(0.0, 0.2)),
[
landElevationPainter,
new TileClassPainter(clLand)
],
avoidClasses(clLand, 3, clPlayerTerritory, 3),
scaleByMapSize(6, 10) * randIntInclusive(8, 14));
g_Map.log("Creating small islands");
createAreas(
new ClumpPlacer(islandSize * randFloat(0.3, 0.7), 0.8, 0.1, 0.07),
[
new SmoothElevationPainter(ELEVATION_SET, heightLand, 6),
new TileClassPainter(clLand)
],
avoidClasses(clLand, 3, clPlayerTerritory, 3),
scaleByMapSize(2, 6) * randIntInclusive(6, 15),
25);
break;
default:
g_Map.log("Creating tight islands");
createAreas(
new ClumpPlacer(islandSize * randFloat(0.8, 1.2), 0.8, 0.1, Infinity),
[
landElevationPainter,
new TileClassPainter(clLand)
],
avoidClasses(clLand, randIntInclusive(8, 16), clPlayerTerritory, 3),
scaleByMapSize(2, 5) * randIntInclusive(8, 14));
}
},
// Disk shaped mainland with water on the edge.
"Continent": () =>
{
const waterHeight = -5;
if (!mapSettings.Nomad)
{
g_Map.log("Ensuring player area");
({ playerIDs, playerPosition } = playerPlacementCircle(fractionToTiles(0.25)));
markPlayerArea("small");
for (let i = 0; i < numPlayers; ++i)
createArea(
new ChainPlacer(
2,
Math.floor(scaleByMapSize(5, 9)),
Math.floor(scaleByMapSize(5, 20)),
Infinity,
playerPosition[i],
0,
[Math.floor(scaleByMapSize(23, 50))]),
[
landElevationPainter,
new TileClassPainter(clLand)
]);
}
g_Map.log("Creating continent");
createArea(
new ClumpPlacer(diskArea(fractionToTiles(0.38)), 0.9, 0.09, Infinity, mapCenter),
[
landElevationPainter,
new TileClassPainter(clLand)
]);
if (randBool(1/3))
{
g_Map.log("Creating peninsula (i.e. half the map not being surrounded by water)");
const angle = randomAngle();
const peninsulaPosition1 =
Vector2D.add(mapCenter,
new Vector2D(fractionToTiles(0.25), 0).rotate(-angle));
createArea(
new ClumpPlacer(diskArea(fractionToTiles(0.38)), 0.9, 0.09, Infinity,
peninsulaPosition1),
[
landElevationPainter,
new TileClassPainter(clLand)
]);
g_Map.log("Remembering to not paint shorelines into the peninsula");
const peninsulaPosition2 =
Vector2D.add(mapCenter,
new Vector2D(fractionToTiles(0.35), 0).rotate(-angle));
createArea(
new ClumpPlacer(diskArea(fractionToTiles(0.33)), 0.9, 0.01, Infinity,
peninsulaPosition2),
new TileClassPainter(clPeninsulaSteam));
}
createShoreJaggedness(waterHeight, clLand, 7);
},
"CentralSea": unknownCentralSeaOrIsthmus.bind(null, false),
"Isthmus": unknownCentralSeaOrIsthmus.bind(null, true),
"CentralRiverLand": unknownCentralRiver.bind(null, true),
"CentralRiverNaval": unknownCentralRiver.bind(null, false),
// Creates a circular lake in the middle and possibly a river
// between each player ("pizza slices").
"RiversAndLake": () =>
{
const waterHeight = -4;
createArea(
new MapBoundsPlacer(),
new ElevationPainter(heightLand));
let startAngle;
if (!mapSettings.Nomad)
{
({ playerIDs, playerPosition, startAngle } =
playerPlacementCircle(fractionToTiles(0.35)));
markPlayerArea("small");
}
const lake = randBool(3/4);
if (lake)
{
g_Map.log("Creating lake");
createArea(
new ClumpPlacer(diskArea(fractionToTiles(0.17)), 0.7, 0.1, Infinity,
mapCenter),
[
new SmoothElevationPainter(ELEVATION_SET, waterHeight, 4),
new TileClassPainter(clWater)
]);
createShoreJaggedness(waterHeight, clWater, 3);
}
// TODO: On nomad because the resource imbalances per island
// are too drastic
{
g_Map.log("Creating small rivers separating players");
for (const river of distributePointsOnCircle(numPlayers,
startAngle + Math.PI / numPlayers, fractionToTiles(0.5), mapCenter)[0])
{
createArea(
new PathPlacer(mapCenter, river, scaleByMapSize(14, 24), 0.4,
3 * scaleByMapSize(1, 3), 0.2, 0.05),
[
new SmoothElevationPainter(ELEVATION_SET, waterHeight, 4),
new TileClassPainter(clWater)
],
avoidClasses(clPlayer, 5));
createArea(
new ClumpPlacer(diskArea(scaleByMapSize(4, 22)), 0.95, 0.6, Infinity,
river),
[
new SmoothElevationPainter(ELEVATION_SET, waterHeight, 0),
new TileClassPainter(clWater)
],
avoidClasses(clPlayer, 5));
}
g_Map.log("Creating small lake");
createArea(
new ClumpPlacer(diskArea(fractionToTiles(0.04)), 0.7, 0.1, Infinity,
mapCenter),
[
new SmoothElevationPainter(ELEVATION_SET, waterHeight, 4),
new TileClassPainter(clWater)
]);
}
if (!mapSettings.Nomad && lake && randBool(2/3))
{
g_Map.log("Creating small central island");
createArea(
new ClumpPlacer(diskArea(fractionToTiles(0.05)), 0.7, 0.1, Infinity,
mapCenter),
[
landElevationPainter,
new TileClassPainter(clWater)
]);
}
},
// Align players on a land strip with seas bordering on one or both
// sides that can hold islands.
"EdgeSeas": () =>
{
const waterHeight = -4;
createArea(
new MapBoundsPlacer(),
new ElevationPainter(heightLand));
const startAngle = randomAngle();
if (!mapSettings.Nomad)
{
playerIDs = sortAllPlayers();
playerPosition =
playerPlacementLine(startAngle + Math.PI / 2, mapCenter,
fractionToTiles(0.2));
// Don't place the shoreline inside the CC, but possibly
// into the players territory
markPlayerArea("small");
}
for (const side of pickRandom([[0], [Math.PI], [0, Math.PI]]))
paintRiver({
"parallel": true,
"start": new Vector2D(mapBounds.left, mapBounds.top)
.rotateAround(side + startAngle, mapCenter),
"end": new Vector2D(mapBounds.left, mapBounds.bottom)
.rotateAround(side + startAngle, mapCenter),
"width": scaleByMapSize(80, randFloat(270, 320)),
"fadeDist": scaleByMapSize(2, 8),
"deviation": 0,
"heightRiverbed": waterHeight,
"heightLand": heightLand,
"meanderShort": 20,
"meanderLong": 0
});
createExtensionsOrIslands();
paintTileClassBasedOnHeight(0, heightCliff, 1, clLand);
createShoreJaggedness(waterHeight, clLand, 7, false);
},
// Land shaped like a concrescent moon around a central lake.
"Gulf": () =>
{
const waterHeight = -3;
createArea(
new MapBoundsPlacer(),
new ElevationPainter(heightLand));
const startAngle = randomAngle();
if (!mapSettings.Nomad)
{
g_Map.log("Determining player locations");
playerPosition = playerPlacementCustomAngle(
fractionToTiles(0.35),
mapCenter,
i => startAngle + 2/3 * Math.PI *
(-1 + (numPlayers == 1 ? 1 : 2 * i / (numPlayers - 1))))[0];
markPlayerArea("large");
}
for (const gulfPart of
[
[0.16, 0],
[0.2, 0.2],
[0.22, 0.49]
])
{
const [radius, distance] = gulfPart.map(fractionToTiles);
const position = Vector2D.sub(mapCenter,
new Vector2D(distance, 0).rotate(-startAngle)).round();
createArea(
new ClumpPlacer(diskArea(radius), 0.7, 0.05, Infinity, position),
[
new SmoothElevationPainter(ELEVATION_SET, waterHeight, 4),
new TileClassPainter(clWater)
],
avoidClasses(clPlayerTerritory, defaultPlayerBaseRadius()));
}
},
// Mainland style with some small random lakes.
"Lakes": () =>
{
const waterHeight = -5;
createArea(
new MapBoundsPlacer(),
new ElevationPainter(heightLand));
if (!mapSettings.Nomad)
{
({ playerIDs, playerPosition } = playerPlacementCircle(fractionToTiles(0.35)));
markPlayerArea("large");
}
g_Map.log("Creating lakes");
createAreas(
new ClumpPlacer(scaleByMapSize(160, 700), 0.2, 0.1, Infinity),
[
new SmoothElevationPainter(ELEVATION_SET, waterHeight, 5),
new TileClassPainter(clWater)
],
[
avoidClasses(clPlayerTerritory, 12),
randBool() ? avoidClasses(clWater, 8) : []
].flat(),
scaleByMapSize(5, 16));
},
// A large hill leaving players only a small passage to each of the
// the two neighboring players.
"Passes": () =>
{
const heightMountain = 24;
const waterHeight = -4;
createArea(
new MapBoundsPlacer(),
new ElevationPainter(heightLand));
let startAngle;
if (!mapSettings.Nomad)
{
({ playerIDs, playerPosition, startAngle } =
playerPlacementCircle(fractionToTiles(0.35)));
markPlayerArea("small");
}
else
startAngle = randomAngle();
g_Map.log("Creating a mountain range between neighboring players");
for (const mountain of distributePointsOnCircle(numPlayers,
startAngle + Math.PI / numPlayers,
fractionToTiles(0.5), mapCenter)[0])
{
createArea(
new PathPlacer(mapCenter, mountain, scaleByMapSize(14, 24), 0.4,
3 * scaleByMapSize(1, 3), 0.2, 0.05),
[
// More smoothing than this often results in
// the mountainrange becoming passable to
// one player.
new SmoothElevationPainter(ELEVATION_SET, heightMountain, 1),
new TileClassPainter(clWater)
],
avoidClasses(clPlayer, 5));
// Small mountain at the map border between the players
// to ensure separation of players
createArea(
new ClumpPlacer(diskArea(scaleByMapSize(4, 22)), 0.95, 0.6, Infinity,
mountain),
new SmoothElevationPainter(ELEVATION_SET, heightMountain, 0),
avoidClasses(clPlayer, 5));
}
g_Map.log("Creating passages between neighboring players");
if (numPlayers > 1)
{
const getEndpoints = (() =>
{
if (numPlayers !== 2)
return i => [i, (i + 1) % numPlayers]
.map(index => playerPosition[index]);
const passes = distributePointsOnCircle(numPlayers * 3, startAngle,
fractionToTiles(0.35), mapCenter)[0];
return i => [1, 2].map(p => passes[3 * i + p]);
})();
for (let i = 0; i < numPlayers; ++i)
{
// For numPlayers > 2 use the playerPosition to
// not end up inside the mountains.
createArea(
new PathPlacer(
...getEndpoints(i),
scaleByMapSize(14, 24),
0.4,
3 * scaleByMapSize(1, 3),
0.2,
0.05),
new SmoothElevationPainter(ELEVATION_SET, heightLand, 2));
}
}
if (randBool(2/5))
{
g_Map.log("Create central lake");
createArea(
new ClumpPlacer(diskArea(fractionToTiles(0.1)), 0.7, 0.1, Infinity,
mapCenter),
[
new SmoothElevationPainter(ELEVATION_SET, waterHeight, 3),
new TileClassPainter(clWater)
]);
}
else
{
g_Map.log("Fill area between the paths");
createArea(
new ClumpPlacer(diskArea(fractionToTiles(0.05)), 0.7, 0.1, Infinity,
mapCenter),
[
new SmoothElevationPainter(ELEVATION_SET, heightMountain, 4),
new TileClassPainter(clWater)
]);
}
},
// Land enclosed by a hill that leaves small areas for civic centers
// and large central place.
"Lowlands": () =>
{
const heightMountain = 30;
g_Map.log("Creating mountain that is going to separate players");
createArea(
new MapBoundsPlacer(),
new ElevationPainter(heightMountain));
let startAngle;
if (!mapSettings.Nomad)
{
({ playerIDs, playerPosition, startAngle } =
playerPlacementCircle(fractionToTiles(0.35)));
markPlayerArea("small");
}
else
startAngle = randomAngle();
g_Map.log("Creating valleys enclosed by the mountain");
let valleys = numPlayers;
if (mapSize >= 128 && numPlayers <= 2 ||
mapSize >= 192 && numPlayers <= 3 ||
mapSize >= 320 && numPlayers <= 4 ||
mapSize >= 384 && numPlayers <= 5 ||
mapSize >= 448 && numPlayers <= 6)
{
valleys *= 2;
}
g_Map.log("Creating player valley");
for (const valley of distributePointsOnCircle(valleys, startAngle, fractionToTiles(0.35),
mapCenter)[0])
{
createArea(
new ClumpPlacer(diskArea(scaleByMapSize(18, 32)), 0.65, 0.1, Infinity,
valley),
[
new SmoothElevationPainter(ELEVATION_SET, heightLand, 2),
new TileClassPainter(clLand)
]);
// Passage from player to center
createArea(
new PathPlacer(mapCenter, valley, scaleByMapSize(14, 24), 0.4,
3 * scaleByMapSize(1, 3), 0.2, 0.05),
[
landElevationPainter,
new TileClassPainter(clWater)
]);
}
g_Map.log("Creating the big central area");
createArea(
new ClumpPlacer(diskArea(fractionToTiles(0.18)), 0.7, 0.1, Infinity, mapCenter),
[
landElevationPainter,
new TileClassPainter(clWater)
]);
},
// No water, no hills.
"Mainland": () =>
{
createArea(
new MapBoundsPlacer(),
new ElevationPainter(3));
if (!mapSettings.Nomad)
{
({ playerIDs, playerPosition } = playerPlacementCircle(fractionToTiles(0.35)));
markPlayerArea("small");
}
}
};
function centralRiverCoordinates(angle)
{
return [
new Vector2D(mapBounds.left + 1, mapCenter.y),
new Vector2D(mapBounds.right - 1, mapCenter.y)
].map(v => v.rotateAround(angle, mapCenter));
}
function createShoreJaggedness(waterHeight, borderClass, shoreDist, inwards = true)
{
g_Map.log("Creating shore jaggedness");
for (let i = 0; i < 2; ++i)
if (i || inwards)
createAreas(
new ChainPlacer(2, Math.floor(scaleByMapSize(4, 6)), 15, Infinity),
[
new SmoothElevationPainter(ELEVATION_SET, i ? heightLand : waterHeight,
4),
i ? new TileClassPainter(clLand) : new TileClassUnPainter(clLand)
],
[
avoidClasses(clPlayer, 20, clPeninsulaSteam, 20),
borderClasses(borderClass, shoreDist, shoreDist)
],
scaleByMapSize(7, 130) * 2,
150);
}
function createExtensionsOrIslands()
{
const rnd = randIntInclusive(1, 3);
if (rnd == 1)
{
g_Map.log("Creating islands");
createAreas(
new ClumpPlacer(Math.square(randIntInclusive(scaleByMapSize(8, 15),
scaleByMapSize(15, 23))), 0.8, 0.1, randFloat(0, 0.2)),
[
landElevationPainter,
new TileClassPainter(clLand)
],
avoidClasses(clLand, 3, clPlayer, 3),
scaleByMapSize(2, 5) * randIntInclusive(8, 14));
}
else if (rnd == 2)
{
g_Map.log("Creating extentions");
createAreas(
new ChainPlacer(Math.floor(scaleByMapSize(4, 7)), Math.floor(scaleByMapSize(7, 10)),
Math.floor(scaleByMapSize(16, 40)), 0.07),
[
landElevationPainter,
new TileClassPainter(clLand)
],
null,
scaleByMapSize(2, 5) * randIntInclusive(8, 14));
}
}
/**
* Prevent impassable terrain and resource collisions at the the civic
* center and starting resources.
*/
function markPlayerArea(size)
{
for (const position of playerPosition)
{
addCivicCenterAreaToClass(position, clPlayer);
if (size == "large")
createArea(
new ClumpPlacer(diskArea(scaleByMapSize(17, 29) / 3), 0.6, 0.3, Infinity,
position),
new TileClassPainter(clPlayerTerritory));
}
}
(mapSettings.Landscape ? unknownMapFunctions[mapSettings.Landscape] :
pickRandom(Object.values(unknownMapFunctions)))();
paintTerrainBasedOnHeight(heightCliff, 40, 1, tCliff);
paintTerrainBasedOnHeight(3, heightCliff, 1, tMainTerrain);
paintTerrainBasedOnHeight(1, 3, 1, tShore);
paintTerrainBasedOnHeight(-8, 1, 2, tWater);
unPaintTileClassBasedOnHeight(0, heightCliff, 1, clWater);
unPaintTileClassBasedOnHeight(-6, 0, 1, clLand);
paintTileClassBasedOnHeight(-6, 0, 1, clWater);
paintTileClassBasedOnHeight(0, heightCliff, 1, clLand);
paintTileClassBasedOnHeight(heightCliff, 40, 1, clHill);
placePlayerBases({
"PlayerPlacement": [playerIDs, playerPosition],
"BaseResourceClass": clBaseResource,
"Walls": g_StartingWalls,
"CityPatch": {
"outerTerrain": tRoadWild,
"innerTerrain": tRoad,
"painters": [
new TileClassPainter(clPlayer)
]
},
"StartingAnimal": {
},
"Berries": {
"template": oFruitBush
},
"Mines": {
"types": [
{ "template": oMetalLarge },
{ "template": oStoneLarge }
]
},
"Treasures": {
"types": [
{
"template": oWoodTreasure,
"count": g_StartingTreasures ? 14 : 0
}
]
},
"Trees": {
"template": oTree1
},
"Decoratives": {
"template": aGrassShort
}
});
// Place resources and decoratives after the player territory was marked.
g_Map.log("Creating bumps");
createAreas(
new ClumpPlacer(scaleByMapSize(20, 50), 0.3, 0.06, Infinity),
new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetBump, 2),
[avoidClasses(clWater, 2, clPlayer, 10), stayClasses(clLand, 3)],
randIntInclusive(0, scaleByMapSize(1, 2) * 200));
g_Map.log("Creating hills");
createAreas(
new ClumpPlacer(scaleByMapSize(20, 150), 0.2, 0.1, Infinity),
[
new LayeredPainter([tCliff, tHill], [2]),
new SmoothElevationPainter(ELEVATION_SET, heightHill, 2),
new TileClassPainter(clHill)
],
[avoidClasses(clPlayer, 15, clHill, randIntInclusive(6, 18)), stayClasses(clLand, 0)],
randIntInclusive(0, scaleByMapSize(4, 8))*randIntInclusive(1, scaleByMapSize(4, 9))
);
yield 30;
g_Map.log("Creating forests");
const [numForest, numStragglers] = getTreeCounts(...rBiomeTreeCount(1));
let types = [
[[tForestFloor2, tMainTerrain, pForest1], [tForestFloor2, pForest1]],
[[tForestFloor1, tMainTerrain, pForest2], [tForestFloor1, pForest2]]
];
const size = numForest / (scaleByMapSize(2, 8) * numPlayers);
let num = Math.floor(size / types.length);
for (const type of types)
createAreas(
new ClumpPlacer(numForest / num, 0.1, 0.1, Infinity),
[
new LayeredPainter(type, [2]),
new TileClassPainter(clForest)
],
[
avoidClasses(clPlayer, 20, clForest, randIntInclusive(5, 15), clHill, 2),
stayClasses(clLand, 4)
],
num);
yield 50;
g_Map.log("Creating dirt patches");
const patchCount = (currentBiome() == "generic/savanna" ? 3 : 1) * scaleByMapSize(15, 45);
for (const patchSize of [scaleByMapSize(3, 48), scaleByMapSize(5, 84), scaleByMapSize(8, 128)])
createAreas(
new ClumpPlacer(patchSize, 0.3, 0.06, 0.5),
[
new LayeredPainter(
[
[tMainTerrain, tTier1Terrain],
[tTier1Terrain, tTier2Terrain],
[tTier2Terrain, tTier3Terrain]
],
[1, 1]),
new TileClassPainter(clDirt)
],
[avoidClasses(clForest, 0, clHill, 2, clDirt, 5, clPlayer, 7), stayClasses(clLand, 4)],
patchCount);
g_Map.log("Creating grass patches");
for (const patchSize of [scaleByMapSize(2, 32), scaleByMapSize(3, 48), scaleByMapSize(5, 80)])
createAreas(
new ClumpPlacer(patchSize, 0.3, 0.06, 0.5),
new TerrainPainter(tTier4Terrain),
[avoidClasses(clForest, 0, clHill, 2, clDirt, 5, clPlayer, 7), stayClasses(clLand, 4)],
patchCount);
yield 55;
g_Map.log("Creating stone mines");
createObjectGroupsDeprecated(
new SimpleGroup(
[
new SimpleObject(oStoneSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1),
new SimpleObject(oStoneLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)
],
true,
clRock),
0,
[avoidClasses(clForest, 1, clPlayer, 10, clRock, 10, clHill, 2), stayClasses(clLand, 3)],
randIntInclusive(scaleByMapSize(2, 9), scaleByMapSize(9, 40)),
100);
g_Map.log("Creating small stone quarries");
createObjectGroupsDeprecated(
new SimpleGroup([new SimpleObject(oStoneSmall, 2, 5, 1, 3)], true, clRock),
0,
[avoidClasses(clForest, 1, clPlayer, 10, clRock, 10, clHill, 2), stayClasses(clLand, 3)],
randIntInclusive(scaleByMapSize(2, 9), scaleByMapSize(9, 40)),
100);
g_Map.log("Creating metal mines");
createObjectGroupsDeprecated(
new SimpleGroup([new SimpleObject(oMetalLarge, 1, 1, 0, 4)], true, clMetal),
0,
[avoidClasses(clForest, 1, clPlayer, 10, clMetal, 10, clRock, 5, clHill, 2), stayClasses(clLand, 3)],
randIntInclusive(scaleByMapSize(2, 9), scaleByMapSize(9, 40)),
100);
yield 65;
g_Map.log("Creating small decorative rocks");
createObjectGroupsDeprecated(
new SimpleGroup([new SimpleObject(aRockMedium, 1, 3, 0, 1)], true),
0,
[avoidClasses(clWater, 0, clForest, 0, clPlayer, 0, clHill, 2), stayClasses(clLand, 3)],
scaleByMapSize(16, 262),
50);
g_Map.log("Creating large decorative rocks");
createObjectGroupsDeprecated(
new SimpleGroup(
[new SimpleObject(aRockLarge, 1, 2, 0, 1), new SimpleObject(aRockMedium, 1, 3, 0, 2)],
true),
0,
[avoidClasses(clWater, 0, clForest, 0, clPlayer, 0, clHill, 2), stayClasses(clLand, 3)],
scaleByMapSize(8, 131),
50);
yield 70;
g_Map.log("Creating deer");
createObjectGroupsDeprecated(
new SimpleGroup([new SimpleObject(oMainHuntableAnimal, 5, 7, 0, 4)], true, clFood),
0,
[
avoidClasses(clWater, 0, clForest, 0, clPlayer, 8, clHill, 2, clFood, 20),
stayClasses(clLand, 2)
],
randIntInclusive(numPlayers + 3, 5 * numPlayers + 4),
50);
g_Map.log("Creating berry bush");
createObjectGroupsDeprecated(
new SimpleGroup([new SimpleObject(oFruitBush, 5, 7, 0, 4)], true, clFood),
0,
[
avoidClasses(clWater, 0, clForest, 0, clPlayer, 8, clHill, 2, clFood, 20),
stayClasses(clLand, 2)
],
randIntInclusive(1, 4) * numPlayers + 2,
50);
yield 75;
g_Map.log("Creating sheep");
createObjectGroupsDeprecated(
new SimpleGroup([new SimpleObject(oSecondaryHuntableAnimal, 2, 3, 0, 2)], true, clFood),
0,
[
avoidClasses(clWater, 0, clForest, 0, clPlayer, 8, clHill, 2, clFood, 20),
stayClasses(clLand, 2)
],
randIntInclusive(numPlayers + 3, 5 * numPlayers + 4),
50);
g_Map.log("Creating fish");
createObjectGroupsDeprecated(
new SimpleGroup([new SimpleObject(oFish, 2, 3, 0, 2)], true, clFood),
0,
avoidClasses(clLand, 4, clForest, 0, clPlayer, 0, clHill, 2, clFood, 20),
randIntInclusive(15, 40) * numPlayers,
60);
yield 85;
g_Map.log("Creating straggler trees");
types = [g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree4];
num = Math.floor(numStragglers / types.length);
for (const type of types)
createObjectGroupsDeprecated(
new SimpleGroup([new SimpleObject(type, 1, 1, 0, 3)], true, clForest),
0,
[
avoidClasses(
clWater, 1,
clForest, 1,
clHill, 2,
clPlayer, 0,
clMetal, 6,
clRock, 6,
clBaseResource, 6),
stayClasses(clLand, 4)
],
num);
const planetm = currentBiome() == "generic/india" ? 8 : 1;
g_Map.log("Creating small grass tufts");
createObjectGroupsDeprecated(
new SimpleGroup([new SimpleObject(aGrassShort, 1, 2, 0, 1, -Math.PI / 8, Math.PI / 8)]),
0,
[avoidClasses(clWater, 2, clHill, 2, clPlayer, 2, clDirt, 0), stayClasses(clLand, 3)],
planetm * scaleByMapSize(13, 200));
yield 90;
g_Map.log("Creating large grass tufts");
createObjectGroupsDeprecated(
new SimpleGroup(
[
new SimpleObject(aGrass, 2, 4, 0, 1.8, -Math.PI / 8, Math.PI / 8),
new SimpleObject(aGrassShort, 3, 6, 1.2, 2.5, -Math.PI / 8, Math.PI / 8)
]),
0,
[
avoidClasses(clWater, 3, clHill, 2, clPlayer, 2, clDirt, 1, clForest, 0),
stayClasses(clLand, 3)
],
planetm * scaleByMapSize(13, 200));
yield 95;
g_Map.log("Creating shallow flora");
createObjectGroupsDeprecated(
new SimpleGroup([new SimpleObject(aLillies, 1, 2, 0, 2), new SimpleObject(aReeds, 2, 4, 0, 2)]),
0,
stayClasses(clShallow, 1),
60 * scaleByMapSize(13, 200),
80);
g_Map.log("Creating bushes");
createObjectGroupsDeprecated(
new SimpleGroup(
[new SimpleObject(aBushMedium, 1, 2, 0, 2), new SimpleObject(aBushSmall, 2, 4, 0, 2)]),
0,
[avoidClasses(clWater, 1, clHill, 2, clPlayer, 1, clDirt, 1), stayClasses(clLand, 3)],
planetm * scaleByMapSize(13, 200),
50);
setSkySet(pickRandom(["cirrus", "cumulus", "sunny", "sunny 1", "mountainous", "stratus"]));
setSunRotation(randomAngle());
setSunElevation(Math.PI * randFloat(1/5, 1/3));
placePlayersNomad(clPlayer,
avoidClasses(clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2, clWater, 10));
return g_Map;
}