Index: binaries/data/mods/public/maps/random/alpine_mountains.js =================================================================== --- binaries/data/mods/public/maps/random/alpine_mountains.js +++ binaries/data/mods/public/maps/random/alpine_mountains.js @@ -0,0 +1,654 @@ +Engine.LoadLibrary("rmgen"); +Engine.LoadLibrary("rmgen-common"); + +const tForestfloor = "alpine_forrestfloor"; +const tForestfloorSnow = "alpine_forrestfloor_snow"; +const tGrassA = "grass_field_a"; +const tGrassB = "grass_field_b"; +const tGrassC = "grass_moss"; +const tRoad = "alpine_dirt"; +const tCliff = "temp_cliff_c"; +const tMontainTop = "alpine_cliff_snow"; +const tMontainPeak = "alpine_snow_a"; + +const oPine = "gaia/flora_tree_pine"; +const oPineSnow = "gaia/flora_tree_pine_w" +const oBerryBush = "gaia/flora_bush_berry"; +const oDeer = "gaia/fauna_deer"; +const oGoat = "gaia/fauna_goat"; +const oWolf = "gaia/fauna_wolf"; +const oStoneLarge = "gaia/geology_stonemine_alpine_quarry"; +const oStoneSmallA = "gaia/geology_stone_alpine_a"; +const oStoneSmallB = "gaia/geology_stone_temperate"; +const oMetalLarge = "gaia/geology_metal_alpine_slabs"; +const oMetalSmall = "gaia/geology_metal_alpine"; + +const aDryGrass = "actor|props/flora/grass_soft_dry_small_tall.xml"; +const aGrassFieldDry = "actor|props/flora/grass_temp_field_dry.xml"; +const aStoneSnow1 = "actor|geology/snow1.xml"; +const aStoneSnow2 = "actor|geology/snow2.xml"; +const aStoneDecoratives = [ + "actor|geology/highland1.xml", + "actor|geology/highland2.xml", + "actor|geology/highland3.xml", + "actor|geology/highland_c.xml", + "actor|geology/highland_d.xml", + "actor|geology/highland_e.xml", + "actor|geology/gray1.xml", + ]; +const aBlowingSnow = "actor|particle/blowing_snow.xml"; + +const pForestPine = [tForestfloor + TERRAIN_SEPARATOR + oPine, tForestfloor]; +const pForestPineSnow = [tForestfloorSnow + TERRAIN_SEPARATOR + oPineSnow, tForestfloorSnow]; + + +const heightLand = 30; +const heightOffsetBump = 2; + +const surroundingPlayerAreaMin = 20; +const surroundingPlayerAreaMax = 50; + +var g_Map = new RandomMap(heightLand, tGrassA); + +const numPlayers = getNumPlayers(); +const mapSize = g_Map.getSize(); +const mapCenter = g_Map.getCenter(); +const mapBounds = g_Map.getBounds(); + +const neighbouringPlayerTiles = 50; + +var clPlayer; +var clMountain; +var clForest; +var clHill = g_Map.createTileClass(); +var clDirt = g_Map.createTileClass(); +var clGrass = g_Map.createTileClass(); +var clRock = g_Map.createTileClass(); +var clMetal = g_Map.createTileClass(); +var clFood = g_Map.createTileClass(); +var clWolf =g_Map.createTileClass(); +var clBaseResource = g_Map.createTileClass(); + +var surroundingPlayersAreas; +var mountainBorderArea; + +var startAngle = randomAngle(); + +var playerIDs = sortAllPlayers(); +var playerPosition; + +Engine.SetProgress(5); + +while(true) { + g_Map = new RandomMap(heightLand, tGrassA); + + clMountain = g_Map.createTileClass(); + clForest = g_Map.createTileClass(); + clPlayer = g_Map.createTileClass(); + surroundingPlayersAreas = []; + + // Perlin/simplex noise is used to create the mountains. + // Multiple layers of noise are stacked, each new ones with smaller amplitude but greater scale. + + g_Map.log("Generating terrain height"); + + /*noise.seed(Math.random()); + for (let i = 0; i < mapSize; ++i) + { + for (let j = 0; j < mapSize; ++j) { + let value = noise.simplex2(i / 75, j / 75) - 0.2; + + if (value > 0) { + let position = new Vector2D(i, j); + clMountain.add(position); + g_Map.height[i][j] = (value) * 100 + heightLand; + } + else { + g_Map.height[i][j] = heightLand; + } + } + } + + noise.seed(Math.random()); + for (let i = 0; i < mapSize; ++i) + { + for (let j = 0; j < mapSize; ++j) { + let value = noise.simplex2(i / 50, j / 50); + + g_Map.height[i][j] += (value) * 15; + } + } + + noise.seed(Math.random()); + for (let i = 0; i < mapSize; ++i) + { + for (let j = 0; j < mapSize; ++j) { + let value = noise.simplex2(i / 10, j / 10); + let position = new Vector2D(i, j); + if (clMountain.has(position)) { + g_Map.height[i][j] += (value + 1) * 4; + } + } + } + + noise.seed(Math.random()); + for (let i = 0; i < mapSize; ++i) + { + for (let j = 0; j < mapSize; ++j) { + let value = noise.simplex2(i / 5, j / 5); + let position = new Vector2D(i, j); + if (clMountain.has(position)) { + g_Map.height[i][j] += (value + 1) * 4; + } + } + }*/ + + // ------------------------------------------------------------------------------------- + + var noise = new Noise2D(8); + for (let x = 0; x < mapSize; ++x) + { + for (let y = 0; y < mapSize; ++y) { + let value = noise.get(x / (mapSize + 1.0), y / (mapSize + 1.0)) - 0.54; + + if (value > 0) { + let position = new Vector2D(x, y); + clMountain.add(position); + g_Map.height[x][y] = (value) * 275 + heightLand; + } + else { + g_Map.height[x][y] = heightLand; + } + } + } + /*var noise = new Noise2D(5); + for (let x = 0; x < mapSize; ++x) + { + for (let y = 0; y < mapSize; ++y) { + let value = noise.get(x / (mapSize + 1.0), y / (mapSize + 1.0)) - 0.54; + + if (value > 0) { + let position = new Vector2D(x, y); + clMountain.add(position); + g_Map.height[x][y] = (value) * 275 + heightLand; + } + else { + g_Map.height[x][y] = heightLand; + } + } + }*/ + + noise = new Noise2D(10); + for (let x = 0; x < mapSize; ++x) + { + for (let y = 0; y < mapSize; ++y) { + let value = noise.get(x / (mapSize + 1.0), y / (mapSize + 1.0)) - 0.565; + let position = new Vector2D(x, y); + g_Map.height[x][y] += (value) * 52; + } + } + + noise = new Noise2D(50); + for (let x = 0; x < mapSize; ++x) + { + for (let y = 0; y < mapSize; ++y) { + let value = noise.get(x / (mapSize + 1.0), y / (mapSize + 1.0)); + let position = new Vector2D(x, y); + if (clMountain.has(position)) { + g_Map.height[x][y] += (value) * 12; + } + } + } + + noise = new Noise2D(100); + for (let x = 0; x < mapSize; ++x) + { + for (let y = 0; y < mapSize; ++y) { + let value = noise.get(x / (mapSize + 1.0), y / (mapSize + 1.0)); + let position = new Vector2D(x, y); + if (clMountain.has(position)) { + g_Map.height[x][y] += (value) * 12; + } + } + } + + mountainBorderArea = createArea(new MapBoundsPlacer(), null, borderClasses(clMountain, 0, 8)); + + createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, 0.5, 2), + borderClasses(clMountain, 1, 1) + ); + + createArea( + new MapBoundsPlacer(), + new SmoothingPainter(3, 0.5, 1), + stayClasses(clMountain, 7) + ); + + if (!isNomad()) + { + g_Map.log("Finding player locations"); + let players = playerPlacementRandom( + playerIDs, + avoidClasses(clMountain, 20) + ); + + if (!players) { + g_Map.log("Too few player locations, starting over"); + continue; + } + [playerIDs, playerPosition] = players; + } + + if (!isNomad()) + { + g_Map.log("Flattening initial CC area"); + let playerRadius = defaultPlayerBaseRadius() * 0.8; + for (let position of playerPosition) + createArea( + new ClumpPlacer(diskArea(playerRadius), 0.95, 0.6, Infinity, position), + new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), playerRadius / 2)); + Engine.SetProgress(38); + } + + for (let i = 0; i < numPlayers; ++i) + { + if (isNomad()) + break; + + placePlayerBase({ + "playerID": playerIDs[i], + "playerPosition": playerPosition[i], + "PlayerTileClass": clPlayer, + "Walls": "towers", + "BaseResourceClass": clBaseResource, + "Chicken": { + }, + "Berries": { + "template": oBerryBush + }, + "Mines": { + "types": [ + { "template": oMetalLarge }, + { "template": oStoneLarge } + ] + }, + "Trees": { + "template": oPine, + "count": 30 + } + }); + } + + if (!isNomad()) { + for (let i = 0; i < numPlayers; ++i) { + surroundingPlayersAreas[i] = createArea(new DiskPlacer(surroundingPlayerAreaMax, playerPosition[i]), null, avoidClasses(clPlayer, surroundingPlayerAreaMin)); + } + } + + g_Map.log("Creating forests"); + const treesPerForests = scaleByMapSize(30, 40); + createAreasInAreas( + new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), treesPerForests, 0.5), + [ + new LayeredPainter([tForestfloor, pForestPine], [1]), + new TileClassPainter(clForest) + ], + new AndConstraint([avoidClasses(clPlayer, 15), borderClasses(clMountain, 2, 30)]), + 50, + 0.5, + [mountainBorderArea]); + + if (!isNomad()) { + g_Map.log("Creating player owned forest"); + for (let i = 0; i < numPlayers; ++i) + createAreasInAreas( + new ChainPlacer(3, 3, 30, 0.15), + [ + new LayeredPainter([tForestfloor, pForestPine], [2]), + new TileClassPainter(clForest) + ], + [new AndConstraint([avoidClasses(clPlayer, 15, clMountain, 9), borderClasses(clPlayer, 0, 30)]), new PassableMapAreaConstraint()], + 1, + 100, + [surroundingPlayersAreas[i]]); + } + + + if (isNomad()) { + break; + } + + g_Map.log("Checking if players are connected"); + if (areConnected(playerPosition, [clMountain, clForest])) { + break; + } + g_Map.log("Players are not connected, starting over"); + +} + +Engine.SetProgress(55); + +g_Map.log("Creating primary grass patches"); +createAreas( + new ChainPlacer(1, 2, randIntInclusive(10, 80), 0.6), + [new TerrainPainter(tGrassB), new TileClassPainter(clGrass)], + avoidClasses(clMountain, 0, clForest, 0, clPlayer, 6), + scaleByMapSize(40, 250) +); + +g_Map.log("Creating secondary grass patches"); +createAreas( + new ChainPlacer(1, 2, randIntInclusive(2, 15), 0.6), + [new TerrainPainter(tGrassC), new TileClassPainter(clGrass)], + avoidClasses(clMountain, 0, clForest, 0, clPlayer, 6), + scaleByMapSize(80, 300) +); + +createAreas(new ChainPlacer(1, 10, randIntInclusive(1, 5), 1), + new SmoothElevationPainter(ELEVATION_MODIFY, 0, 100), + new AndConstraint([stayClasses(clMountain, 2), new SlopeConstraint(0.5, 1.7), new HeightConstraint(heightLand, heightLand + 60)]), + scaleByMapSize(7, 35), + 20 + ); + +g_Map.log("Painting mountains"); +createArea( + new MapBoundsPlacer(), + new TerrainPainter(tCliff), + [ + stayClasses(clMountain, 0), + new SlopeConstraint(1.7, Infinity) + ]); + +createArea( + new MapBoundsPlacer(), + new TerrainPainter(tMontainTop), + [ + stayClasses(clMountain, 0), + new SlopeConstraint(0.7, 1.7) + ]); + +createArea( + new MapBoundsPlacer(), + new TerrainPainter(tMontainPeak), + [ + stayClasses(clMountain, 0), + new SlopeConstraint(0, 0.7) + ]); + +createArea( + new MapBoundsPlacer(), + new TerrainPainter(tMontainPeak), + [ + new HeightConstraint(heightLand + 60, Infinity) + ]); + + +createAreas( + new ChainPlacer(1, 3, randIntInclusive(1, 10), 1), + [ + new TerrainPainter(pForestPineSnow[0]), + new TileClassPainter(clForest) + ], + new AndConstraint([stayClasses(clMountain, 2), new SlopeConstraint(0, 1.7), new HeightConstraint(heightLand, heightLand + 40)]), + scaleByMapSize(3, 15), + 20 + ); + +Engine.SetProgress(65); + +g_Map.log("Creating minerals"); +var group = new SimpleGroup([new SimpleObject(oMetalSmall, 1, 4, 0, 1.5)], true, clMetal); +createObjectGroupsByAreas(group, 0, + [avoidClasses(clPlayer, 15, clForest, 1, clMetal, 8, clMountain, 1), borderClasses(clMountain, 0, 3), new SlopeConstraint(0, 1.2)], + Math.floor(scaleByMapSize(8, 55) * randFloat(1, 1.5)), 100, [mountainBorderArea]); + +group = new SimpleGroup([new SimpleObject(oStoneLarge, 1, 1, 0, 1.5)], true, clRock); +createObjectGroupsByAreas(group, 0, + [avoidClasses(clPlayer, 15, clForest, 1, clMetal, 12, clRock, 12), borderClasses(clMountain, 0, 3)], + Math.floor(scaleByMapSize(2, 10) * randFloat(1, 1.5)), 100, [mountainBorderArea]); + +group = new SimpleGroup([new SimpleObject(oStoneSmallA, 1, 4, 0, 1.5)], true, clRock); +createObjectGroupsByAreas(group, 0, + [avoidClasses(clPlayer, 15, clForest, 1, clMetal, 8, clRock, 2, clMountain, 1), borderClasses(clMountain, 0, 3), new SlopeConstraint(0, 1.2)], + Math.floor(scaleByMapSize(4, 20) * randFloat(1, 1.5)), 100, [mountainBorderArea]); + +group = new SimpleGroup([new SimpleObject(oStoneSmallA, 1, 4, 0, 3)], true, clRock); +createObjectGroups(group, 0, + [avoidClasses(clPlayer, 25, clForest, 1, clMetal, 8, clRock, 2, clMountain, 5)], + Math.floor(scaleByMapSize(1, 8) * randFloat(1, 2)), 100); + +group = new SimpleGroup([new SimpleObject(oStoneSmallB, 1, 4, 0, 3)], true, clRock); +createObjectGroups(group, 0, + [avoidClasses(clPlayer, 25, clForest, 1, clMetal, 8, clRock, 6, clMountain, 8)], + Math.floor(scaleByMapSize(1, 8) * randFloat(1, 2)), 100); + +Engine.SetProgress(75); + +if (!isNomad()) { + g_Map.log("Creating additionnal food for players (balance)"); + // Player ressource balance calculation + var initialFoodAmount = randIntInclusive(0, 25);// 1 unit = 100 food + + // I want it likely that there is no additionnal food for the player. + if (initialFoodAmount < 6) + initialFoodAmount = 0; + + for (let i = 0; i < numPlayers; ++i) { + let remainingFood = initialFoodAmount; + while (remainingFood > 0) { + if (remainingFood <= 2) { + remainingFood = 0; + } + else if (remainingFood <= 4) { + placeFoodForPlayer(oGoat, remainingFood, remainingFood, remainingFood, i); + remainingFood = 0; + } + else if (remainingFood <= 10) { + remainingFood -= placeFoodForPlayer(pickRandom([oDeer, oGoat]), 5, 8, remainingFood, i); + } + else { + if (randBool(0.5)) { + remainingFood -= 2 * placeFoodForPlayer(oBerryBush, 5, 7, Math.floor(remainingFood/2), i); + } + else { + remainingFood -= placeFoodForPlayer(pickRandom([oDeer, oGoat]), 5, 8, remainingFood, i); + } + } + } + } +} + +Engine.SetProgress(85); + +g_Map.log("Creating berries"); +group = new SimpleGroup( + [new SimpleObject(oBerryBush, 4,6, 0,4)], + true, clFood +); +createObjectGroups(group, 0, + [avoidClasses(clPlayer, neighbouringPlayerTiles, clMountain, 3, clMetal, 4, clRock, 4, clFood, 10), borderClasses(clForest, 1, 1)], + randFloat(0.5, 1) * scaleByMapSize(6, 30), 20 +); + +g_Map.log("Creating deer"); +group = new SimpleGroup( + [new SimpleObject(oDeer, 5,7, 0,4)], + true, clFood +); +createObjectGroups(group, 0, + avoidClasses(clForest, 0, clPlayer, neighbouringPlayerTiles, clMountain, 3, clMetal, 4, clRock, 4, clFood, 10), + randFloat(0.2, 1) * scaleByMapSize(2, 10), 5 +); + +g_Map.log("Creating goats"); +group = new SimpleGroup( + [new SimpleObject(oGoat, 5,7, 0,4)], + true, clFood +); +createObjectGroups(group, 0, + avoidClasses(clForest, 0, clPlayer, neighbouringPlayerTiles, clMountain, 3, clMetal, 4, clRock, 4, clFood, 10), + randFloat(0.2, 1) * scaleByMapSize(3, 15), 5 +); + +g_Map.log("Creating wolves"); +group = new SimpleGroup( + [new SimpleObject(oWolf, 1,3, 0,4)], + true, clWolf +); +createObjectGroups(group, 0, + avoidClasses(clForest, 0, clPlayer, neighbouringPlayerTiles, clMountain, 3, clMetal, 4, clRock, 4, clFood, 1), + randFloat(0.2, 1) * scaleByMapSize(2, 10), 5 +); + +Engine.SetProgress(90); + +g_Map.log("Creating straggler trees"); +createStragglerTrees([oPine], + avoidClasses(clMountain, 3, clPlayer, 15, clMetal, 4, clRock, 4, clFood, 1, clWolf, 1, clForest, 3), + clForest, + scaleByMapSize(40, 380), + 100); + +g_Map.log("Creating straggler trees on mountains"); +createStragglerTrees([oPineSnow], + new AndConstraint([stayClasses(clMountain, 1), new HeightConstraint(heightLand + 5, heightLand + 40), new SlopeConstraint(0, 1)]), + clForest, + scaleByMapSize(200, 1900), + 50); + +g_Map.log("Creating grass"); +group = new SimpleGroup( + [new SimpleObject(aGrassFieldDry, 1, 4, 0,1.8, -Math.PI / 8, Math.PI / 8)] +); +createObjectGroupsDeprecated(group, 0, + avoidClasses(clMountain, 6, clPlayer, 2, clForest, 0), + scaleByMapSize(36, 300) +); + +group = new SimpleGroup( + [new SimpleObject(aDryGrass, 1, 3, 0,1.8, -Math.PI / 8, Math.PI / 8)] +); +createObjectGroupsDeprecated(group, 0, + avoidClasses(clMountain, 6, clPlayer, 2, clForest, 0), + scaleByMapSize(36, 300) +); + +g_Map.log("Creating decorative rocks"); +group = new SimpleGroup( + [new RandomObject(aStoneDecoratives, 1, 3, 0,1.8, -Math.PI / 8, Math.PI / 8)] +); +createObjectGroupsDeprecated(group, 0, + avoidClasses(clMountain, 0, clPlayer, 2, clForest, 0), + scaleByMapSize(12, 100) +); + +group = new SimpleGroup( + [new RandomObject([aStoneSnow1, aStoneSnow2], 1, 1, 0,1.8, -Math.PI / 8, Math.PI / 8)] +); +createObjectGroupsDeprecated(group, 0, + new AndConstraint([stayClasses(clMountain, 2), new SlopeConstraint(0, 1.7), new HeightConstraint(heightLand, heightLand + 60)]), + scaleByMapSize(72, 600) +); + +g_Map.log("Creating blowing snow"); +group = new SimpleGroup( + [new SimpleObject(aBlowingSnow, 1, 1, 0,1.8, -Math.PI / 8, Math.PI / 8)] +); +createObjectGroupsDeprecated(group, 0, + new AndConstraint([stayClasses(clMountain, 2), new SlopeConstraint(0, 1.7), new HeightConstraint(heightLand + 30, Infinity)]), + scaleByMapSize(18, 150) +); + +placePlayersNomadConnected(clPlayer, avoidClasses(clForest, 1, clMetal, 4, clRock, 4, clMountain, 4, clFood, 2, clWolf, 25)); + +setSkySet("sunset"); +setSunColor(1, 0.87451, 0.611765); +setSunElevation(0.524621); +setSunRotation(-0.785396); +setTerrainAmbientColor(0.501961, 0.501961, 0.501961); +setUnitsAmbientColor(0.501961, 0.501961, 0.501961); +setFogFactor(0.00046875); +setFogThickness(0.14); +setFogColor(0.8, 0.8, 0.8); + +g_Map.ExportMap(); + +// Places a bounded amount of corresponding type food to the specified player and returns the amount placed. +function placeFoodForPlayer(type, min, max, remainingFood, areaId) { + // Since the placing function doesn't specify (i think ?) the number of objects placed, randomization is done there. + max = max < remainingFood ? max : remainingFood; + let amountPlaced = randIntInclusive(min, max); + + // Hunt should spawn farther from the CC in general. + let minTileBound = 20; + let maxTileBound = 30; + if (type != oBerryBush) { + minTileBound += 5; + maxTileBound += 5; + } + + let food = new SimpleGroup( + [new SimpleObject(type, amountPlaced, amountPlaced, 0,4)], + true, clFood + ); + createObjectGroupsByAreas(food, 0, + new AndConstraint([avoidClasses(clForest, 0, clPlayer, minTileBound, clMountain, 1, clMetal, 4, clRock, 4, clFood, 10), borderClasses(clPlayer, 0, maxTileBound)]), + 1, 400, [surroundingPlayersAreas[areaId]] + ); + + return amountPlaced; +} + +function placePlayersNomadConnected(playerClass, constraints) +{ + if (!isNomad()) + return undefined; + + g_Map.log("Placing nomad starting units"); + + let distance = scaleByMapSize(60, 240); + + let numPlayers = getNumPlayers(); + let playerIDs = shuffleArray(sortAllPlayers()); + let playerPosition = []; + let constraint = new AndConstraint(constraints); + do { + for (let i = 0; i < numPlayers; ++i) { + let validCoordinates = false; + let position; + do { + position = g_Map.randomCoordinate(); + } while (!constraint.allows(position)); + playerPosition[i] = position; + } + } while (!areConnected(playerPosition, [clMountain, clForest])); + + for (let i = 0; i < numPlayers; ++i) + { + let objects = getStartingEntities(playerIDs[i]).filter(ents => ents.Template.startsWith("units/")).map( + ents => new SimpleObject(ents.Template, ents.Count || 1, ents.Count || 1, 1, 3)); + + // Add treasure if too few resources for a civic center + let ccCost = Engine.GetTemplate("structures/" + getCivCode(playerIDs[i]) + "_civil_centre").Cost.Resources; + for (let resourceType in ccCost) + { + let treasureTemplate = g_NomadTreasureTemplates[resourceType]; + + let count = Math.max(0, Math.ceil( + (ccCost[resourceType] - (g_MapSettings.StartingResources || 0)) / + Engine.GetTemplate(treasureTemplate).ResourceSupply.Amount)); + + objects.push(new SimpleObject(treasureTemplate, count, count, 3, 5)); + } + + // Try place these entities at a random location + let group = new SimpleGroup(objects, true, playerClass); + group.setCenterPosition(playerPosition[i]); + group.place(i + 1, new NullConstraint()); + } + + return [playerIDs, playerPosition]; +} Index: binaries/data/mods/public/maps/random/alpine_mountains.json =================================================================== --- binaries/data/mods/public/maps/random/alpine_mountains.json +++ binaries/data/mods/public/maps/random/alpine_mountains.json @@ -0,0 +1,9 @@ +{ + "settings" : { + "Name" : "Alpine Mountains", + "Script" : "alpine_mountains.js", + "Description" : "Alpine mountains", + "Preview" : "lorraine_plain.png", + "CircularMap" : true + } +}