Line data Source code
1 : /**
2 : * @file These functions are often used to place gaia entities, like forests, mines, animals or decorative bushes.
3 : */
4 :
5 : /**
6 : * Returns the number of trees in forests and straggler trees.
7 : */
8 : function getTreeCounts(minTrees, maxTrees, forestRatio)
9 : {
10 0 : return [forestRatio, 1 - forestRatio].map(p => p * scaleByMapSize(minTrees, maxTrees));
11 : }
12 :
13 : /**
14 : * Places uniformly sized forests at random locations.
15 : * Unless you want a custom number of forest, prefer createDefaultForests.
16 : * Generates two variants of forests from the given terrain textures and tree templates.
17 : * The forest border has less trees than the inside.
18 : * @param terrainsSet - a list of 5 terrains to use. The first 3 are border terrains, the later 2 interior.
19 : * @param constraint - constraints to respect
20 : * @param tileClass - the tileclass to print
21 : * @param treeCount - Either { "nbForests": X, "treesPerForest": X } or (legacy) a number of trees.
22 : * @param retryFactor - @see createAreas
23 : */
24 : function createForests(terrainSet, constraint, tileClass, treeCount, retryFactor)
25 : {
26 0 : if (!treeCount)
27 0 : return;
28 :
29 : // Construct different forest types from the terrain textures and template names.
30 0 : const [mainTerrain, terrainForestFloor1, terrainForestFloor2, terrainForestTree1, terrainForestTree2] = terrainSet;
31 :
32 : // The painter will pick a random Terrain for each part of the forest.
33 0 : const forestVariants = [
34 : {
35 : "borderTerrains": [terrainForestFloor2, mainTerrain, terrainForestTree1],
36 : "interiorTerrains": [terrainForestFloor2, terrainForestTree1]
37 : },
38 : {
39 : "borderTerrains": [terrainForestFloor1, mainTerrain, terrainForestTree2],
40 : "interiorTerrains": [terrainForestFloor1, terrainForestTree2]
41 : }
42 : ];
43 :
44 : let numberOfTrees;
45 : let numberOfForests;
46 0 : if (typeof treeCount === "number")
47 : {
48 0 : numberOfTrees = treeCount;
49 0 : numberOfForests = Math.floor(numberOfTrees / (scaleByMapSize(3, 6) * getNumPlayers() * forestVariants.length));
50 : }
51 : else
52 : {
53 0 : numberOfForests = treeCount.nbForests;
54 0 : numberOfTrees = numberOfForests * treeCount.treesPerForest;
55 : }
56 :
57 0 : if (!numberOfForests)
58 0 : return;
59 :
60 0 : g_Map.log("Creating forests");
61 0 : for (const forestVariant of forestVariants)
62 0 : createAreas(
63 : new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), numberOfTrees / numberOfForests, 0.5),
64 : [
65 : new LayeredPainter([forestVariant.borderTerrains, forestVariant.interiorTerrains], [2]),
66 : new TileClassPainter(tileClass)
67 : ],
68 : constraint,
69 : numberOfForests,
70 : retryFactor);
71 : }
72 :
73 0 : var g_DefaultNumberOfForests = scaleByMapSize(8, 36);
74 :
75 : /**
76 : * Passes some sane defaults to createForests.
77 : */
78 : function createDefaultForests(terrainSet, constraints, tileClass, totalNumberOfTrees)
79 : {
80 0 : return createForests(
81 : terrainSet,
82 : constraints,
83 : tileClass,
84 : {
85 : "nbForests": g_DefaultNumberOfForests,
86 : "treesPerForest": totalNumberOfTrees / g_DefaultNumberOfForests,
87 : }
88 : );
89 : }
90 :
91 :
92 : /**
93 : * Places the given amount of Entities at random places meeting the given Constraint, chosing a different template for each.
94 : */
95 : function createStragglerTrees(templateNames, constraint, tileClass, treeCount, retryFactor)
96 : {
97 0 : g_Map.log("Creating straggler trees");
98 0 : for (let templateName of templateNames)
99 0 : createObjectGroupsDeprecated(
100 : new SimpleGroup([new SimpleObject(templateName, 1, 1, 0, 3)], true, tileClass),
101 : 0,
102 : constraint,
103 : Math.floor(treeCount / templateNames.length),
104 : retryFactor);
105 : }
106 :
107 : /**
108 : * Places a SimpleGroup consisting of the given number of the given Objects
109 : * at random locations that meet the given Constraint.
110 : */
111 : function createMines(objects, constraint, tileClass, count)
112 : {
113 0 : for (let object of objects)
114 0 : createObjectGroupsDeprecated(
115 : new SimpleGroup(object, true, tileClass),
116 : 0,
117 : constraint,
118 : count || scaleByMapSize(4, 16),
119 : 70);
120 : }
121 :
122 : /**
123 : * Place large/small mines on the map in such a way that it should be relatively fair.
124 : * @param oSmall - the small mine object
125 : * @param oLarge - the large mine object
126 : * @param clMine - the 'mine' class to paint.
127 : * @param constraints - Custom constraints. Note that the function automatically avoids clMine as well.
128 : * @param counts - a dict of numbers { "largeCount": 10, "smallCount": 100, "randomSmallCount": 2 }
129 : * @param randomness - randomize counts by a random multiplier between [1 - randomness, 1 + randomness]
130 : */
131 : function createBalancedMines(oSmall, oLarge, clMine, constraints, counts, randomness)
132 : {
133 0 : let largeCount = counts.largeCount;
134 0 : let smallCount = counts.smallCount;
135 0 : let randomSmallCount = counts.randomSmallCount;
136 :
137 0 : if (randomness > 0 && randomness < 1)
138 : {
139 0 : largeCount = Math.round(largeCount * randFloat(1 - randomness, 1 + randomness));
140 0 : smallCount = Math.round(smallCount * randFloat(1 - randomness, 1 + randomness));
141 0 : randomSmallCount = Math.round(randomSmallCount * randFloat(1 - randomness, 1 + randomness));
142 : }
143 :
144 0 : const arrayConstraints = Array.isArray(constraints) ? constraints : [constraints];
145 :
146 : // Plop large mines far away from each other.
147 0 : createObjectGroups(
148 : new SimpleGroup([new SimpleObject(oLarge, 1, 1, 0, 1)], true, clMine),
149 : 0,
150 : new AndConstraint([avoidClasses(clMine, scaleByMapSize(25, 50)), ...arrayConstraints]),
151 : largeCount,
152 : 100);
153 :
154 : // Plop smaller clusters of small mines, also somewhat farther away.
155 0 : createObjectGroups(
156 : new SimpleGroup([new SimpleObject(oSmall, 2, 3, 0, 2)], true, clMine),
157 : 0,
158 : new AndConstraint([avoidClasses(clMine, scaleByMapSize(18, 35)), ...arrayConstraints]),
159 : smallCount,
160 : 50);
161 :
162 : // Plop a few smaller clusters in a random fashion, occasionally making very good dropsites spots.
163 0 : createObjectGroups(
164 : new SimpleGroup([new SimpleObject(oSmall, 1, 2, 0, 2)], true, clMine),
165 : 0,
166 : new AndConstraint([avoidClasses(clMine, 5), ...arrayConstraints]),
167 : randomSmallCount,
168 : 50);
169 : }
170 :
171 : /**
172 : * Helper for createBalancedMines with default metal counts.
173 : * The current settings are so that a Small 1v1 has about 40K metal,
174 : * and a Normal 4v4 has about 140K.
175 : * The setup is biaised so that with fewer players, there are more small mines,
176 : * and with more players there are proportionally more big mines, to maintain
177 : * some randomness to the distribution but keep it somewhat fair in 1v1.
178 : */
179 : function createBalancedMetalMines(oSmall, oLarge, clMine, constraints, counts = 1.0, randomness = 0.05)
180 : {
181 0 : return createBalancedMines(
182 : oSmall,
183 : oLarge,
184 : clMine,
185 : constraints,
186 : {
187 : "largeCount": (Math.max(scaleByMapSize(1, 9), getNumPlayers() * 1.8 - 0.8)) * counts,
188 : "smallCount": (scaleByMapSize(4, 12)) * counts,
189 : "randomSmallCount": (scaleByMapSize(1, 8)) * counts,
190 : },
191 : randomness
192 : );
193 : }
194 :
195 : /**
196 : * Helper for createBalancedMines with default stone counts.
197 : * There is a little less stone than metal overall.
198 : */
199 : function createBalancedStoneMines(oSmall, oLarge, clMine, constraints, counts = 1.0, randomness = 0.05)
200 : {
201 0 : return createBalancedMines(
202 : oSmall,
203 : oLarge,
204 : clMine,
205 : constraints,
206 : {
207 : "largeCount": (Math.max(scaleByMapSize(1, 9), getNumPlayers() * 1.25)) * counts,
208 : "smallCount": (scaleByMapSize(1, 8)) * counts,
209 : "randomSmallCount": (scaleByMapSize(1, 8)) * counts,
210 : },
211 : randomness
212 : );
213 : }
214 :
215 :
216 : /**
217 : * Places Entities of the given templateName in a circular pattern (leaving out a quarter of the circle).
218 : */
219 : function createStoneMineFormation(position, templateName, terrain, radius = 2.5, count = 8, startAngle = undefined, maxOffset = 1)
220 : {
221 0 : createArea(
222 : new ChainPlacer(radius / 2, radius, 2, Infinity, position, undefined, [5]),
223 : new TerrainPainter(terrain));
224 :
225 0 : let angle = startAngle !== undefined ? startAngle : randomAngle();
226 :
227 0 : for (let i = 0; i < count; ++i)
228 : {
229 0 : let pos = Vector2D.add(position, new Vector2D(radius + randFloat(0, maxOffset), 0).rotate(-angle)).round();
230 0 : g_Map.placeEntityPassable(templateName, 0, pos, randomAngle());
231 0 : angle += 3/2 * Math.PI / count;
232 : }
233 : }
234 :
235 : /**
236 : * Places the given amounts of the given Objects at random locations meeting the given Constraint.
237 : */
238 : function createFood(objects, counts, constraint, tileClass)
239 : {
240 0 : g_Map.log("Creating food");
241 0 : for (let i = 0; i < objects.length; ++i)
242 0 : createObjectGroupsDeprecated(
243 : new SimpleGroup(objects[i], true, tileClass),
244 : 0,
245 : constraint,
246 : counts[i],
247 : 50);
248 : }
249 :
250 : /**
251 : * Same as createFood, but doesn't mark the terrain with a TileClass.
252 : */
253 : function createDecoration(objects, counts, constraint)
254 : {
255 0 : g_Map.log("Creating decoration");
256 0 : for (let i = 0; i < objects.length; ++i)
257 0 : createObjectGroupsDeprecated(
258 : new SimpleGroup(objects[i], true),
259 : 0,
260 : constraint,
261 : counts[i],
262 : 5);
263 : }
264 :
265 : /**
266 : * Places docks in situations where the location of land and water is not known in advance.
267 : * Do determine the position, it picks a random point on the land, find the closest, significantly large body of water,
268 : * then places the dock at the first point close to that body of water within the given heightrange.
269 : *
270 : * @param {string} template - The template name of the dock to be placed.
271 : * @param {number} playerID - The owner of the dock.
272 : * @param {number} count - The number of docks to be placed.
273 : * @param {Object} tileClassWater - The tileclass the water area is marked with.
274 : * @param {Object} tileClassDock - The dock position is marked with this class.
275 : * @param {number} heightMin - The lowest height a dock could be placed.
276 : * @param {number} heightMax - The greatest height a dock could be placed.
277 : * @param {Array|Constraint} constraints - Only consider dock positions valid that meet this Constraint.
278 : * @param {number} offset - How many tiles to move the dock towards the direction of the water after having found a location.
279 : * @param {number} retryFactor- How many different locations should be tested.
280 : */
281 : function placeDocks(template, playerID, count, tileClassWater, tileClassDock, heightMin, heightMax, constraints, offset, retryFactor)
282 : {
283 0 : let mapCenter = g_Map.getCenter();
284 :
285 0 : g_Map.log("Marking dock search start area");
286 0 : let areaSearchStart = createArea(
287 : new DiskPlacer(fractionToTiles(0.5) - 10, mapCenter),
288 : undefined,
289 : avoidClasses(tileClassWater, 6));
290 :
291 0 : g_Map.log("Marking dock search end area");
292 0 : let areaSearchEnd = createArea(
293 : new DiskPlacer(fractionToTiles(0.5) - 10, mapCenter),
294 : undefined,
295 : stayClasses(tileClassWater, 20));
296 :
297 0 : g_Map.log("Marking land area");
298 0 : let areaLand = createArea(
299 : new MapBoundsPlacer(),
300 : undefined,
301 : avoidClasses(tileClassWater, 0));
302 :
303 0 : g_Map.log("Marking water area");
304 0 : let areaWater = createArea(
305 : new MapBoundsPlacer(),
306 : undefined,
307 : stayClasses(tileClassWater, 0));
308 :
309 0 : if (!areaSearchEnd || !areaSearchEnd.getPoints().length)
310 0 : return;
311 :
312 : // TODO: computing the exact intersection with the waterplane would both not require us to pass reasonable heights and be more precise
313 0 : let constraint = new AndConstraint(constraints);
314 0 : g_Map.log("Placing docks");
315 0 : for (let i = 0; i < count; ++i)
316 0 : for (let tries = 0; tries < retryFactor; ++tries)
317 : {
318 0 : let positionLand = pickRandom(areaSearchStart.getPoints());
319 0 : let positionWaterLarge = areaSearchEnd.getClosestPointTo(positionLand);
320 0 : let positionDock = findLocationInDirectionBasedOnHeight(positionWaterLarge, positionLand, heightMin, heightMax, offset);
321 0 : if (!positionDock)
322 0 : continue;
323 :
324 0 : positionDock.round();
325 :
326 0 : if (!g_Map.inMapBounds(positionDock) || !constraint.allows(positionDock))
327 0 : continue;
328 :
329 0 : let angle = positionDock.angleTo(Vector2D.average(new DiskPlacer(8, positionDock).place(stayClasses(tileClassWater, 0))));
330 :
331 0 : g_Map.placeEntityPassable(template, playerID, positionDock, -angle + Math.PI / 2);
332 0 : tileClassDock.add(positionDock);
333 0 : break;
334 : }
335 : }
|