Unify Caledonian Meadows and Wild Lake player location duplication from rP19704 ...
[0ad.git] / binaries / data / mods / public / maps / random / caledonian_meadows.js
blob3897ef282956bf093bc91a9231f72c3147fd5076
1 RMS.LoadLibrary("rmgen");
2 RMS.LoadLibrary("rmbiome");
3 RMS.LoadLibrary("heightmap");
5 InitMap();
7 let genStartTime = Date.now();
9 /**
10  * Drags a path to a target height smoothing it at the edges and return some points along the path.
11  */
12 function placeRandomPathToHeight(
13         start, target, targetHeight, tileClass = undefined, texture = "road_rome_a",
14         width = 10, distance = 4, strength = 0.08, heightmap = g_Map.height)
16         let pathPoints = [];
17         let position = clone(start);
18         while (true)
19         {
20                 rectangularSmoothToHeight(position, width, width, targetHeight, strength, heightmap);
21                 if (texture)
22                 {
23                         let painters = [new TerrainPainter(texture)];
24                         if (tileClass !== undefined)
25                                 painters.push(paintClass(tileClass));
26                         createArea(
27                                 new ClumpPlacer(0.3 * Math.square(width), 1, 1, 1, Math.floor(position.x), Math.floor(position.y)),
28                                 painters);
29                 }
30                 pathPoints.push({ "x": position.x, "y": position.y, "dist": distance });
31                 // Check for distance to target and setup for next loop if needed
32                 if (Math.euclidDistance2D(position.x, position.y, target.x, target.y) < distance / 2)
33                         break;
34                 let angleToTarget = getAngle(position.x, position.y, target.x, target.y);
35                 let angleOff = randFloat(-PI/2, PI/2);
36                 position.x += distance * cos(angleToTarget + angleOff);
37                 position.y += distance * sin(angleToTarget + angleOff);
38         }
39         return pathPoints;
42 /**
43  * Design resource spots
44  */
45 // Mines
46 let decorations = [
47         "actor|geology/gray1.xml", "actor|geology/gray_rock1.xml",
48         "actor|geology/highland1.xml", "actor|geology/highland2.xml", "actor|geology/highland3.xml",
49         "actor|geology/highland_c.xml", "actor|geology/highland_d.xml", "actor|geology/highland_e.xml",
50         "actor|props/flora/bush.xml", "actor|props/flora/bush_dry_a.xml", "actor|props/flora/bush_highlands.xml",
51         "actor|props/flora/bush_tempe_a.xml", "actor|props/flora/bush_tempe_b.xml", "actor|props/flora/ferns.xml"
54 function placeMine(point, centerEntity)
56         placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI));
57         let quantity = randIntInclusive(11, 23);
58         let dAngle = TWO_PI / quantity;
59         for (let i = 0; i < quantity; ++i)
60         {
61                 let angle = dAngle * randFloat(i, i + 1);
62                 let dist = randFloat(2, 5);
63                 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(decorations), 0, randFloat(0, 2 * PI));
64         }
67 // Food, fences with domestic animals
68 wallStyles.other.sheepIn = new WallElement("sheepIn", "gaia/fauna_sheep", PI / 4, -1.5, 0.75, PI/2);
69 wallStyles.other.foodBin = new WallElement("foodBin", "gaia/special_treasure_food_bin", PI/2, 1.5);
70 wallStyles.other.sheep = new WallElement("sheep", "gaia/fauna_sheep", 0, 0, 0.75);
71 wallStyles.other.farm = new WallElement("farm", "structures/brit_farmstead", PI, 0, -3);
72 let fences = [
73         new Fortress("fence", ["foodBin", "farm", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence"]),
74         new Fortress("fence", ["foodBin", "farm", "fence", "sheepIn", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence"]),
75         new Fortress("fence", [
76                 "foodBin", "farm", "cornerIn", "bench", "cornerOut", "fence_short", "sheepIn", "fence", "sheepIn",
77                 "fence", "sheepIn", "fence_short", "sheep", "fence"
78         ]),
79         new Fortress("fence", [
80                 "foodBin", "farm", "cornerIn", "fence_short", "cornerOut", "bench", "sheepIn", "fence", "sheepIn",
81                 "fence", "sheepIn", "fence_short", "sheep", "fence"
82         ]),
83         new Fortress("fence", [
84                 "foodBin", "farm", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn",
85                 "fence_short", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence"
86         ])
88 let num = fences.length;
89 for (let i = 0; i < num; ++i)
90         fences.push(new Fortress("fence", clone(fences[i].wall).reverse()));
92 // Groves, only Wood
93 let groveEntities = ["gaia/flora_bush_temperate", "gaia/flora_tree_euro_beech"];
94 let groveActors = [
95         "actor|geology/highland1_moss.xml", "actor|geology/highland2_moss.xml",
96         "actor|props/flora/bush.xml", "actor|props/flora/bush_dry_a.xml", "actor|props/flora/bush_highlands.xml",
97         "actor|props/flora/bush_tempe_a.xml", "actor|props/flora/bush_tempe_b.xml", "actor|props/flora/ferns.xml"
99 let clGrove = createTileClass();
101 function placeGrove(point)
103         placeObject(point.x, point.y, pickRandom(["structures/gaul_outpost", "gaia/flora_tree_oak_new"]), 0, randFloat(0, 2 * PI));
104         let quantity = randIntInclusive(20, 30);
105         let dAngle = TWO_PI / quantity;
106         for (let i = 0; i < quantity; ++i)
107         {
108                 let angle = dAngle * randFloat(i, i + 1);
109                 let dist = randFloat(2, 5);
110                 let objectList = groveEntities;
111                 if (i % 3 == 0)
112                         objectList = groveActors;
113                 let x = point.x + dist * Math.cos(angle);
114                 let y = point.y + dist * Math.sin(angle);
115                 placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * PI));
116                 createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter("temp_grass_plants"), paintClass(clGrove)]);
117         }
120 // Camps with fire and gold treasure
121 function placeCamp(point,
122         centerEntity = "actor|props/special/eyecandy/campfire.xml",
123         otherEntities = ["gaia/special_treasure_metal", "gaia/special_treasure_standing_stone",
124                 "units/brit_infantry_slinger_b", "units/brit_infantry_javelinist_b", "units/gaul_infantry_slinger_b", "units/gaul_infantry_javelinist_b", "units/gaul_champion_fanatic",
125                 "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"
126         ]
129         placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI));
130         let quantity = randIntInclusive(5, 11);
131         let dAngle = TWO_PI / quantity;
132         for (let i = 0; i < quantity; ++i)
133         {
134                 let angle = dAngle * randFloat(i, i + 1);
135                 let dist = randFloat(1, 3);
136                 placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(otherEntities), 0, randFloat(0, 2 * PI));
137         }
140 function placeStartLocationResources(point, foodEntities = ["gaia/flora_bush_berry", "gaia/fauna_chicken", "gaia/fauna_chicken"])
142         let currentAngle = randFloat(0, TWO_PI);
143         // Stone and chicken
144         let dAngle = TWO_PI * 2 / 9;
145         let angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
146         let dist = 12;
147         let x = point.x + dist * Math.cos(angle);
148         let y = point.y + dist * Math.sin(angle);
149         placeMine({ "x": x, "y": y }, "gaia/geology_stonemine_temperate_quarry");
151         currentAngle += dAngle;
153         // Wood
154         let quantity = 80;
155         dAngle = TWO_PI / quantity / 3;
156         for (let i = 0; i < quantity; ++i)
157         {
158                 angle = currentAngle + randFloat(0, dAngle);
159                 dist = randFloat(10, 15);
160                 let objectList = groveEntities;
161                 if (i % 2 == 0)
162                         objectList = groveActors;
163                 x = point.x + dist * Math.cos(angle);
164                 y = point.y + dist * Math.sin(angle);
165                 placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * PI));
166                 createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter("temp_grass_plants"), paintClass(clGrove)]);
167                 currentAngle += dAngle;
168         }
170         // Metal and chicken
171         dAngle = TWO_PI * 2 / 9;
172         angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
173         dist = 13;
174         x = point.x + dist * Math.cos(angle);
175         y = point.y + dist * Math.sin(angle);
176         placeMine({ "x": x, "y": y }, "gaia/geology_metal_temperate_slabs");
177         currentAngle += dAngle;
179         // Berries
180         quantity = 15;
181         dAngle = TWO_PI / quantity * 2 / 9;
182         for (let i = 0; i < quantity; ++i)
183         {
184                 angle = currentAngle + randFloat(0, dAngle);
185                 dist = randFloat(10, 15);
186                 x = point.x + dist * Math.cos(angle);
187                 y = point.y + dist * Math.sin(angle);
188                 placeObject(x, y, pickRandom(foodEntities), 0, randFloat(0, 2 * PI));
189                 currentAngle += dAngle;
190         }
193 log("Functions loaded after " + ((Date.now() - genStartTime) / 1000) + "s");
196  * Environment settings
197  */
198 setBiome("alpine");
199 g_Environment.Fog.FogColor = { "r": 0.8, "g": 0.8, "b": 0.8, "a": 0.01 };
200 g_Environment.Water.WaterBody.Colour = { "r" : 0.3, "g" : 0.05, "b" : 0.1, "a" : 0.1 };
201 g_Environment.Water.WaterBody.Murkiness = 0.4;
204  * Base terrain shape generation and settings
205  */
206  // Height range by map size
207 let heightScale = (g_Map.size + 256) / 768 / 4;
208 let heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale };
210 // Water coverage
211 let averageWaterCoverage = 1/5; // NOTE: Since terrain generation is quite unpredictable actual water coverage might vary much with the same value
212 let waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); // Water height in environment and the engine
213 let waterHeightAdjusted = waterHeight + MIN_HEIGHT; // Water height in RMGEN
214 setWaterHeight(waterHeight);
216 // Generate base terrain shape
217 let medH = (heightRange.min + heightRange.max) / 2;
218 let initialHeightmap = [[medH, medH], [medH, medH]];
219 setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8);
221 // Apply simple erosion
222 for (let i = 0; i < 5; ++i)
223         splashErodeMap(0.1);
225 // Final rescale
226 rescaleHeightmap(heightRange.min, heightRange.max);
228 RMS.SetProgress(25);
231  * Prepare terrain texture placement
232  */
233 let heighLimits = [
234         heightRange.min + 1/3 * (waterHeightAdjusted - heightRange.min), // 0 Deep water
235         heightRange.min + 2/3 * (waterHeightAdjusted - heightRange.min), // 1 Medium Water
236         heightRange.min + (waterHeightAdjusted - heightRange.min), // 2 Shallow water
237         waterHeightAdjusted + 1/8 * (heightRange.max - waterHeightAdjusted), // 3 Shore
238         waterHeightAdjusted + 2/8 * (heightRange.max - waterHeightAdjusted), // 4 Low ground
239         waterHeightAdjusted + 3/8 * (heightRange.max - waterHeightAdjusted), // 5 Player and path height
240         waterHeightAdjusted + 4/8 * (heightRange.max - waterHeightAdjusted), // 6 High ground
241         waterHeightAdjusted + 5/8 * (heightRange.max - waterHeightAdjusted), // 7 Lower forest border
242         waterHeightAdjusted + 6/8 * (heightRange.max - waterHeightAdjusted), // 8 Forest
243         waterHeightAdjusted + 7/8 * (heightRange.max - waterHeightAdjusted), // 9 Upper forest border
244         waterHeightAdjusted + (heightRange.max - waterHeightAdjusted)]; // 10 Hilltop
245 let playerHeight = (heighLimits[4] + heighLimits[5]) / 2; // Average player height
247 // Texture and actor presets
248 let myBiome = [];
249 myBiome.push({ // 0 Deep water
250         "texture": ["shoreline_stoney_a"],
251         "actor": [["gaia/fauna_fish", "actor|geology/stone_granite_boulder.xml"], 0.02],
252         "textureHS": ["alpine_mountainside"], "actorHS": [["gaia/fauna_fish"], 0.1]
254 myBiome.push({ // 1 Medium Water
255         "texture": ["shoreline_stoney_a", "alpine_shore_rocks"],
256         "actor": [["actor|geology/stone_granite_boulder.xml", "actor|geology/stone_granite_med.xml"], 0.03],
257         "textureHS": ["alpine_mountainside"], "actorHS": [["actor|geology/stone_granite_boulder.xml", "actor|geology/stone_granite_med.xml"], 0.0]
259 myBiome.push({ // 2 Shallow water
260         "texture": ["alpine_shore_rocks"],
261         "actor": [["actor|props/flora/reeds_pond_dry.xml", "actor|geology/stone_granite_large.xml", "actor|geology/stone_granite_med.xml", "actor|props/flora/reeds_pond_lush_b.xml"], 0.2],
262         "textureHS": ["alpine_mountainside"], "actorHS": [["actor|props/flora/reeds_pond_dry.xml", "actor|geology/stone_granite_med.xml"], 0.1]
264 myBiome.push({ // 3 Shore
265         "texture": ["alpine_shore_rocks_grass_50", "alpine_grass_rocky"],
266         "actor": [["gaia/flora_tree_pine", "gaia/flora_bush_badlands", "actor|geology/highland1_moss.xml", "actor|props/flora/grass_soft_tuft_a.xml", "actor|props/flora/bush.xml"], 0.3],
267         "textureHS": ["alpine_mountainside"], "actorHS": [["actor|props/flora/grass_soft_tuft_a.xml"], 0.1]
269 myBiome.push({ // 4 Low ground
270         "texture": ["alpine_dirt_grass_50", "alpine_grass_rocky"],
271         "actor": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_soft_tuft_a.xml", "actor|props/flora/bush.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2],
272         "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_soft_tuft_a.xml"], 0.1]
274 myBiome.push({ // 5 Player and path height
275         "texture": ["new_alpine_grass_c", "new_alpine_grass_b", "new_alpine_grass_d"],
276         "actor": [["actor|geology/stone_granite_small.xml", "actor|props/flora/grass_soft_small.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2],
277         "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_small.xml", "actor|props/flora/grass_soft_small.xml"], 0.1]
279 myBiome.push({ // 6 High ground
280         "texture": ["new_alpine_grass_a", "alpine_grass_rocky"],
281         "actor": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_tufts_a.xml", "actor|props/flora/bush_highlands.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2],
282         "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_tufts_a.xml"], 0.1]
284 myBiome.push({ // 7 Lower forest border
285         "texture": ["new_alpine_grass_mossy", "alpine_grass_rocky"],
286         "actor": [["gaia/flora_tree_pine", "gaia/flora_tree_oak", "actor|props/flora/grass_tufts_a.xml", "gaia/flora_bush_berry", "actor|geology/highland2_moss.xml", "gaia/fauna_goat", "actor|props/flora/bush_tempe_underbrush.xml"], 0.3],
287         "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|props/flora/grass_tufts_a.xml", "actor|geology/highland2_moss.xml"], 0.1]
289 myBiome.push({ // 8 Forest
290         "texture": ["alpine_forrestfloor"],
291         "actor": [["gaia/flora_tree_pine", "gaia/flora_tree_pine", "gaia/flora_tree_pine", "gaia/flora_tree_pine", "actor|geology/highland2_moss.xml", "actor|props/flora/bush_highlands.xml"], 0.5],
292         "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|geology/highland2_moss.xml", "actor|geology/stone_granite_med.xml"], 0.1]
294 myBiome.push({ // 9 Upper forest border
295         "texture": ["alpine_forrestfloor_snow", "new_alpine_grass_dirt_a"],
296         "actor": [["gaia/flora_tree_pine", "actor|geology/snow1.xml"], 0.3],
297         "textureHS": ["alpine_cliff_b"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|geology/snow1.xml"], 0.1]
299 myBiome.push({ // 10 Hilltop
300         "texture": ["alpine_cliff_a", "alpine_cliff_snow"],
301         "actor": [["actor|geology/highland1.xml"], 0.05],
302         "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|geology/highland1.xml"], 0.0]
305 log("Terrain shape generation and texture presets after " + ((Date.now() - genStartTime) / 1000) + "s");
307 let [playerIDs, startLocations] = sortPlayersByLocation(getStartLocationsByHeightmap({ "min": heighLimits[4], "max": heighLimits[5] }, 1000, 30));
309 log("Start location chosen after " + ((Date.now() - genStartTime) / 1000) + "s");
310 RMS.SetProgress(30);
313  * Smooth Start Locations before height region calculation
314  */
315 for (let p = 0; p < playerIDs.length; ++p)
316         rectangularSmoothToHeight(startLocations[p], 35, 35, playerHeight, 0.7);
319  * Add paths
320  */
321 let tchm = getTileCenteredHeightmap(); // Calculate tileCenteredHeightMap (This has nothing to to with TILE_CENTERED_HEIGHT_MAP which should be false)
322 let pathPoints = [];
323 let clPath = createTileClass();
324 for (let i = 0; i < startLocations.length; ++i)
326         let start = startLocations[i];
327         let target = startLocations[(i + 1) % startLocations.length];
328         pathPoints = pathPoints.concat(placeRandomPathToHeight(start, target, playerHeight, clPath));
331 log("Paths placed after " + ((Date.now() - genStartTime) / 1000) + "s");
332 RMS.SetProgress(45);
335  * Get resource spots after players start locations calculation
336  */
337 let avoidPoints = clone(startLocations);
338 for (let i = 0; i < avoidPoints.length; ++i)
339         avoidPoints[i].dist = 30;
340 let resourceSpots = getPointsByHeight({ "min": (heighLimits[3] + heighLimits[4]) / 2, "max": (heighLimits[5] + heighLimits[6]) / 2 }, avoidPoints, clPath);
342 log("Resource spots chosen after " + ((Date.now() - genStartTime) / 1000) + "s");
343 RMS.SetProgress(55);
346  * Divide tiles in areas by height and avoid paths
347  */
348 let areas = [];
349 for (let h = 0; h < heighLimits.length; ++h)
350         areas.push([]);
352 for (let x = 0; x < tchm.length; ++x)
354         for (let y = 0; y < tchm[0].length; ++y)
355         {
356                 if (g_Map.tileClasses[clPath].inclusionCount[x][y] > 0) // Avoid paths
357                         continue;
359                 let minHeight = heightRange.min;
360                 for (let h = 0; h < heighLimits.length; ++h)
361                 {
362                         if (tchm[x][y] >= minHeight && tchm[x][y] <= heighLimits[h])
363                         {
364                                 areas[h].push({ "x": x, "y": y });
365                                 break;
366                         }
367                         else
368                                 minHeight = heighLimits[h];
369                 }
370         }
374  * Get max slope of each area
375  */
376 let slopeMap = getSlopeMap();
377 let minSlope = [];
378 let maxSlope = [];
379 for (let h = 0; h < heighLimits.length; ++h)
381         minSlope[h] = Infinity;
382         maxSlope[h] = 0;
383         for (let t = 0; t < areas[h].length; ++t)
384         {
385                 let x = areas[h][t].x;
386                 let y = areas[h][t].y;
387                 let slope = slopeMap[x][y];
389                 if (slope > maxSlope[h])
390                         maxSlope[h] = slope;
392                 if (slope < minSlope[h])
393                         minSlope[h] = slope;
394         }
398  * Paint areas by height and slope
399  */
400 for (let h = 0; h < heighLimits.length; ++h)
402         for (let t = 0; t < areas[h].length; ++t)
403         {
404                 let x = areas[h][t].x;
405                 let y = areas[h][t].y;
406                 let actor = undefined;
407                 let texture = pickRandom(myBiome[h].texture);
409                 if (slopeMap[x][y] < 0.4 * (minSlope[h] + maxSlope[h]))
410                 {
411                         if (randBool(myBiome[h].actor[1]))
412                                 actor = pickRandom(myBiome[h].actor[0]);
413                 }
414                 else
415                 {
416                         texture = pickRandom(myBiome[h].textureHS);
417                         if (randBool(myBiome[h].actorHS[1]))
418                                 actor = pickRandom(myBiome[h].actorHS[0]);
419                 }
421                 g_Map.texture[x][y] = g_Map.getTextureID(texture);
423                 if (actor)
424                         placeObject(randFloat(x, x + 1), randFloat(y, y + 1), actor, 0, randFloat(0, 2 * PI));
425         }
428 log("Terrain texture placement finished after " + ((Date.now() - genStartTime) / 1000) + "s");
429 RMS.SetProgress(80);
432  * Add start locations and resource spots after terrain texture and path painting
433  */
434 for (let p = 0; p < playerIDs.length; ++p)
436         let point = startLocations[p];
437         placeCivDefaultEntities(point.x, point.y, playerIDs[p], { "iberWall": true });
438         placeStartLocationResources(startLocations[p]);
441 for (let i = 0; i < resourceSpots.length; ++i)
443         let choice = i % 5;
444         if (choice == 0)
445                 placeMine(resourceSpots[i], "gaia/geology_stonemine_temperate_formation");
446         if (choice == 1)
447                 placeMine(resourceSpots[i], "gaia/geology_metal_temperate_slabs");
448         if (choice == 2)
449                 placeCustomFortress(resourceSpots[i].x, resourceSpots[i].y, pickRandom(fences), "other", 0, randFloat(0, 2 * PI));
450         if (choice == 3)
451                 placeGrove(resourceSpots[i]);
452         if (choice == 4)
453                 placeCamp(resourceSpots[i]);
456 log("Map generation finished after " + ((Date.now() - genStartTime) / 1000) + "s");
458 ExportMap();