Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen-common/gaia_entities.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen-common/gaia_entities.js (revision 25810) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen-common/gaia_entities.js (revision 25811) @@ -1,336 +1,335 @@ /** * @file These functions are often used to place gaia entities, like forests, mines, animals or decorative bushes. */ /** * Returns the number of trees in forests and straggler trees. */ function getTreeCounts(minTrees, maxTrees, forestRatio) { return [forestRatio, 1 - forestRatio].map(p => p * scaleByMapSize(minTrees, maxTrees)); } /** * Places uniformly sized forests at random locations. * Unless you want a custom number of forest, prefer createDefaultForests. * Generates two variants of forests from the given terrain textures and tree templates. * The forest border has less trees than the inside. * @param terrainsSet - a list of 5 terrains to use. The first 3 are border terrains, the later 2 interior. * @param constraint - constraints to respect * @param tileClass - the tileclass to print * @param treeCount - Either { "nbForests": X, "treesPerForest": X } or (legacy) a number of trees. * @param retryFactor - @see createAreas */ function createForests(terrainSet, constraint, tileClass, treeCount, retryFactor) { if (!treeCount) return; // Construct different forest types from the terrain textures and template names. const [mainTerrain, terrainForestFloor1, terrainForestFloor2, terrainForestTree1, terrainForestTree2] = terrainSet; // The painter will pick a random Terrain for each part of the forest. const forestVariants = [ { "borderTerrains": [terrainForestFloor2, mainTerrain, terrainForestTree1], "interiorTerrains": [terrainForestFloor2, terrainForestTree1] }, { "borderTerrains": [terrainForestFloor1, mainTerrain, terrainForestTree2], "interiorTerrains": [terrainForestFloor1, terrainForestTree2] } ]; let numberOfTrees; let numberOfForests; if (typeof treeCount === "number") { numberOfTrees = treeCount; numberOfForests = Math.floor(numberOfTrees / (scaleByMapSize(3, 6) * getNumPlayers() * forestVariants.length)); } else { numberOfForests = treeCount.nbForests; numberOfTrees = numberOfForests * treeCount.treesPerForest; } if (!numberOfForests) return; g_Map.log("Creating forests"); for (const forestVariant of forestVariants) createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), numberOfTrees / numberOfForests, 0.5), [ new LayeredPainter([forestVariant.borderTerrains, forestVariant.interiorTerrains], [2]), new TileClassPainter(tileClass) ], constraint, numberOfForests, retryFactor); } var g_DefaultNumberOfForests = scaleByMapSize(8, 36); /** * Passes some sane defaults to createForests. */ function createDefaultForests(terrainSet, constraints, tileClass, totalNumberOfTrees) { return createForests( terrainSet, constraints, tileClass, { "nbForests": g_DefaultNumberOfForests, "treesPerForest": totalNumberOfTrees / g_DefaultNumberOfForests, } ); } /** * Places the given amount of Entities at random places meeting the given Constraint, chosing a different template for each. */ function createStragglerTrees(templateNames, constraint, tileClass, treeCount, retryFactor) { g_Map.log("Creating straggler trees"); for (let templateName of templateNames) createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(templateName, 1, 1, 0, 3)], true, tileClass), 0, constraint, Math.floor(treeCount / templateNames.length), retryFactor); } /** * Places a SimpleGroup consisting of the given number of the given Objects * at random locations that meet the given Constraint. */ function createMines(objects, constraint, tileClass, count) { for (let object of objects) createObjectGroupsDeprecated( new SimpleGroup(object, true, tileClass), 0, constraint, count || scaleByMapSize(4, 16), 70); } /** * Place large/small mines on the map in such a way that it should be relatively fair. * @param oSmall - the small mine object * @param oLarge - the large mine object * @param clMine - the 'mine' class to paint. * @param constraints - Custom constraints. Note that the function automatically avoids clMine as well. - * @param counts - optional, either a single number acting as multiplier to the defaults - * or a dict of numbers{ "largeCount": 0, "smallCount": 1, "randomSmallCount": 2 } + * @param counts - a dict of numbers { "largeCount": 10, "smallCount": 100, "randomSmallCount": 2 } * @param randomness - randomize counts by a random multiplier between [1 - randomness, 1 + randomness] */ -function createBalancedMines(oSmall, oLarge, clMine, constraints, counts = 1.0, randomness = 0.1) +function createBalancedMines(oSmall, oLarge, clMine, constraints, counts, randomness) { let largeCount = counts.largeCount; let smallCount = counts.smallCount; let randomSmallCount = counts.randomSmallCount; if (randomness > 0 && randomness < 1) { largeCount = Math.round(largeCount * randFloat(1 - randomness, 1 + randomness)); smallCount = Math.round(smallCount * randFloat(1 - randomness, 1 + randomness)); randomSmallCount = Math.round(randomSmallCount * randFloat(1 - randomness, 1 + randomness)); } const arrayConstraints = Array.isArray(constraints) ? constraints : [constraints]; // Plop large mines far away from each other. createObjectGroups( new SimpleGroup([new SimpleObject(oLarge, 1, 1, 0, 1)], true, clMine), 0, new AndConstraint([avoidClasses(clMine, scaleByMapSize(25, 50)), ...arrayConstraints]), largeCount, 100); // Plop smaller clusters of small mines, also somewhat farther away. createObjectGroups( new SimpleGroup([new SimpleObject(oSmall, 2, 3, 0, 2)], true, clMine), 0, new AndConstraint([avoidClasses(clMine, scaleByMapSize(18, 35)), ...arrayConstraints]), smallCount, 50); // Plop a few smaller clusters in a random fashion, occasionally making very good dropsites spots. createObjectGroups( new SimpleGroup([new SimpleObject(oSmall, 1, 2, 0, 2)], true, clMine), 0, new AndConstraint([avoidClasses(clMine, 5), ...arrayConstraints]), randomSmallCount, 50); } /** * Helper for createBalancedMines with default metal counts. * The current settings are so that a Small 1v1 has about 40K metal, * and a Normal 4v4 has about 140K. * The setup is biaised so that with fewer players, there are more small mines, * and with more players there are proportionally more big mines, to maintain * some randomness to the distribution but keep it somewhat fair in 1v1. */ function createBalancedMetalMines(oSmall, oLarge, clMine, constraints, counts = 1.0, randomness = 0.05) { return createBalancedMines( oSmall, oLarge, clMine, constraints, { "largeCount": (Math.max(scaleByMapSize(1, 9), getNumPlayers() * 1.8 - 0.8)) * counts, "smallCount": (scaleByMapSize(4, 12)) * counts, "randomSmallCount": (scaleByMapSize(1, 8)) * counts, }, randomness ); } /** * Helper for createBalancedMines with default stone counts. * There is a little less stone than metal overall. */ function createBalancedStoneMines(oSmall, oLarge, clMine, constraints, counts = 1.0, randomness = 0.05) { return createBalancedMines( oSmall, oLarge, clMine, constraints, { "largeCount": (Math.max(scaleByMapSize(1, 9), getNumPlayers() * 1.25)) * counts, "smallCount": (scaleByMapSize(1, 8)) * counts, "randomSmallCount": (scaleByMapSize(1, 8)) * counts, }, randomness ); } /** * Places Entities of the given templateName in a circular pattern (leaving out a quarter of the circle). */ function createStoneMineFormation(position, templateName, terrain, radius = 2.5, count = 8, startAngle = undefined, maxOffset = 1) { createArea( new ChainPlacer(radius / 2, radius, 2, Infinity, position, undefined, [5]), new TerrainPainter(terrain)); let angle = startAngle !== undefined ? startAngle : randomAngle(); for (let i = 0; i < count; ++i) { let pos = Vector2D.add(position, new Vector2D(radius + randFloat(0, maxOffset), 0).rotate(-angle)).round(); g_Map.placeEntityPassable(templateName, 0, pos, randomAngle()); angle += 3/2 * Math.PI / count; } } /** * Places the given amounts of the given Objects at random locations meeting the given Constraint. */ function createFood(objects, counts, constraint, tileClass) { g_Map.log("Creating food"); for (let i = 0; i < objects.length; ++i) createObjectGroupsDeprecated( new SimpleGroup(objects[i], true, tileClass), 0, constraint, counts[i], 50); } /** * Same as createFood, but doesn't mark the terrain with a TileClass. */ function createDecoration(objects, counts, constraint) { g_Map.log("Creating decoration"); for (let i = 0; i < objects.length; ++i) createObjectGroupsDeprecated( new SimpleGroup(objects[i], true), 0, constraint, counts[i], 5); } /** * Places docks in situations where the location of land and water is not known in advance. * Do determine the position, it picks a random point on the land, find the closest, significantly large body of water, * then places the dock at the first point close to that body of water within the given heightrange. * * @param {string} template - The template name of the dock to be placed. * @param {number} playerID - The owner of the dock. * @param {number} count - The number of docks to be placed. * @param {Object} tileClassWater - The tileclass the water area is marked with. * @param {Object} tileClassDock - The dock position is marked with this class. * @param {number} heightMin - The lowest height a dock could be placed. * @param {number} heightMax - The greatest height a dock could be placed. * @param {Array|Constraint} constraints - Only consider dock positions valid that meet this Constraint. * @param {number} offset - How many tiles to move the dock towards the direction of the water after having found a location. * @param {number} retryFactor- How many different locations should be tested. */ function placeDocks(template, playerID, count, tileClassWater, tileClassDock, heightMin, heightMax, constraints, offset, retryFactor) { let mapCenter = g_Map.getCenter(); g_Map.log("Marking dock search start area"); let areaSearchStart = createArea( new DiskPlacer(fractionToTiles(0.5) - 10, mapCenter), undefined, avoidClasses(tileClassWater, 6)); g_Map.log("Marking dock search end area"); let areaSearchEnd = createArea( new DiskPlacer(fractionToTiles(0.5) - 10, mapCenter), undefined, stayClasses(tileClassWater, 20)); g_Map.log("Marking land area"); let areaLand = createArea( new MapBoundsPlacer(), undefined, avoidClasses(tileClassWater, 0)); g_Map.log("Marking water area"); let areaWater = createArea( new MapBoundsPlacer(), undefined, stayClasses(tileClassWater, 0)); if (!areaSearchEnd || !areaSearchEnd.getPoints().length) return; // TODO: computing the exact intersection with the waterplane would both not require us to pass reasonable heights and be more precise let constraint = new AndConstraint(constraints); g_Map.log("Placing docks"); for (let i = 0; i < count; ++i) for (let tries = 0; tries < retryFactor; ++tries) { let positionLand = pickRandom(areaSearchStart.getPoints()); let positionWaterLarge = areaSearchEnd.getClosestPointTo(positionLand); let positionDock = findLocationInDirectionBasedOnHeight(positionWaterLarge, positionLand, heightMin, heightMax, offset); if (!positionDock) continue; positionDock.round(); if (!g_Map.inMapBounds(positionDock) || !constraint.allows(positionDock)) continue; let angle = positionDock.angleTo(Vector2D.average(new DiskPlacer(8, positionDock).place(stayClasses(tileClassWater, 0)))); g_Map.placeEntityPassable(template, playerID, positionDock, -angle + Math.PI / 2); tileClassDock.add(positionDock); break; } }