1 /////////////////////////////////////////////////////////////////////////////////////////
4 // Function for creating shallow water between two given points by changing the height of all tiles in
5 // the path with height less than or equal to "maxheight" to "height"
7 // x1,z1: Starting point of path
8 // x2,z2: Ending point of path
9 // width: Width of the shallow
10 // maxheight: Maximum height that it changes
11 // height: Height of the shallow
12 // smooth: smooth elevation in borders
13 // tileclass: (Optianal) - Adds those tiles to the class given
14 // terrain: (Optional) - Changes the texture of the elevated land
16 /////////////////////////////////////////////////////////////////////////////////////////
18 function passageMaker(x1, z1, x2, z2, width, maxheight, height, smooth, tileclass, terrain, riverheight)
20 var tchm = TILE_CENTERED_HEIGHT_MAP;
21 TILE_CENTERED_HEIGHT_MAP = true;
22 var mapSize = g_Map.size;
23 for (var ix = 0; ix < mapSize; ix++)
25 for (var iz = 0; iz < mapSize; iz++)
29 var c = (z1*(x1-x2))-(x1*(z1-z2));
30 var dis = abs(a*ix + b*iz + c)/sqrt(a*a + b*b);
31 var k = (a*ix + b*iz + c)/(a*a + b*b);
37 if ((iz <= Math.max(z1,z2))&&(iz >= Math.min(z1,z2)))
44 if ((my <= Math.max(z1,z2))&&(my >= Math.min(z1,z2)))
49 if ((dis <= width)&&(inline))
51 if(g_Map.getHeight(ix, iz) <= maxheight)
53 if (dis > width - smooth)
55 g_Map.setHeight(ix, iz, ((width - dis)*(height)+(riverheight)*(smooth - width + dis))/(smooth));
57 else if (dis <= width - smooth)
59 g_Map.setHeight(ix, iz, height);
61 if (tileclass !== undefined)
63 addToClass(ix, iz, tileclass);
65 if (terrain !== undefined)
67 placeTerrain(ix, iz, terrain);
73 TILE_CENTERED_HEIGHT_MAP = tchm;
76 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
77 //rndRiver is a fuction that creates random values useful for making a jagged river.
79 //it works the same as sin or cos function. the only difference is that it's period is 1 instead of 2*pi
80 //it needs the "seed" parameter to use it to make random curves that don't get broken.
81 //seed must be created using randFloat(). or else it won't work
83 // f: Input: Same as angle in a sine function
84 // seed: Random Seed: Best to implement is to use randFloat()
86 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
88 function rndRiver(f, seed)
93 var rndRr = f-floor(f);
95 for (var rndRx=0; rndRx<=floor(f); rndRx++)
97 rndRw = 10*(rndRw-floor(rndRw));
107 rndRe = (floor(rndRw))%5;
110 rndRa = (rndRs)*2.3*(rndRr)*(rndRr-1)*(rndRr-0.5)*(rndRr-0.5);
114 rndRa = (rndRs)*2.6*(rndRr)*(rndRr-1)*(rndRr-0.3)*(rndRr-0.7);
118 rndRa = (rndRs)*22*(rndRr)*(rndRr-1)*(rndRr-0.2)*(rndRr-0.3)*(rndRr-0.3)*(rndRr-0.8);
122 rndRa = (rndRs)*180*(rndRr)*(rndRr-1)*(rndRr-0.2)*(rndRr-0.2)*(rndRr-0.4)*(rndRr-0.6)*(rndRr-0.6)*(rndRr-0.8);
126 rndRa = (rndRs)*2.6*(rndRr)*(rndRr-1)*(rndRr-0.5)*(rndRr-0.7);
132 * Creates a meandering river at the given location and width.
133 * Optionally calls a function on the affected tiles.
135 * @property horizontal - Whether the river is horizontal or vertical
136 * @property parallel - Whether the shorelines should be parallel or meander separately.
137 * @property position - Location of the river. Number between 0 and 1.
138 * @property width - Size between the two shorelines. Number between 0 and 1.
139 * @property fadeDist - Size of the shoreline.
140 * @property deviation - Fuzz effect on the shoreline if greater than 0.
141 * @property waterHeight - Ground height of the riverbed.
142 * @proeprty landHeight - Ground height of the end of the shoreline.
143 * @property meanderShort - Strength of frequent meanders.
144 * @property meanderLong - Strength of less frequent meanders.
145 * @property waterFunc - Optional function called on water tiles, providing ix, iz, height.
146 * @property landFunc - Optional function called on land tiles, providing ix, iz, shoreDist1, shoreDist2.
148 function paintRiver(args)
150 log("Creating the river");
152 let theta1 = randFloat(0, 1);
153 let theta2 = randFloat(0, 1);
155 let seed1 = randFloat(2, 3);
156 let seed2 = randFloat(2, 3);
158 let meanderShort = args.meanderShort / scaleByMapSize(35, 160);
159 let meanderLong = args.meanderLong / scaleByMapSize(35, 100);
161 let mapSize = g_Map.size;
163 for (let ix = 0; ix < mapSize; ++ix)
164 for (let iz = 0; iz < mapSize; ++iz)
166 if (args.constraint && !args.constraint.allows(ix, iz))
169 let x = ix / (mapSize + 1.0);
170 let z = iz / (mapSize + 1.0);
172 let coord1 = args.horizontal ? z : x;
173 let coord2 = args.horizontal ? x : z;
175 // River curve at this place
176 let cu1 = meanderShort * rndRiver(theta1 + coord2 * mapSize / 128, seed1);
177 let cu2 = meanderShort * rndRiver(theta2 + coord2 * mapSize / 128, seed2);
179 cu1 += meanderLong * rndRiver(theta2 + coord2 * mapSize / 256, seed2);
180 cu2 += meanderLong * rndRiver(theta2 + coord2 * mapSize / 256, seed2);
184 // Fuzz the river border
185 let devCoord1 = coord1 * randFloat(1 - args.deviation, 1 + args.deviation);
186 let devCoord2 = coord2 * randFloat(1 - args.deviation, 1 + args.deviation);
188 let shoreDist1 = -devCoord1 + cu1 + args.position - args.width / 2;
189 let shoreDist2 = -devCoord1 + cu2 + args.position + args.width / 2;
191 if (shoreDist1 < 0 && shoreDist2 > 0)
193 let height = args.waterHeight;
195 if (shoreDist1 > -args.fadeDist)
196 height += (args.landHeight - args.waterHeight) * (1 + shoreDist1 / args.fadeDist);
197 else if (shoreDist2 < args.fadeDist)
198 height += (args.landHeight - args.waterHeight) * (1 - shoreDist2 / args.fadeDist);
200 setHeight(ix, iz, height);
203 args.waterFunc(ix, iz, height);
205 else if (args.landFunc)
206 args.landFunc(ix, iz, shoreDist1, shoreDist2);
210 /////////////////////////////////////////////////////////////////////////////////////////
211 // createStartingPlayerEntities
213 // Creates the starting player entities
214 // fx&fz: position of player base
215 // playerid: id of player
216 // civEntities: use getStartingEntities(id-1) fo this one
217 // orientation: orientation of the main base building, default is BUILDING_ORIENTATION
219 ///////////////////////////////////////////////////////////////////////////////////////////
220 function createStartingPlayerEntities(fx, fz, playerid, civEntities, orientation = BUILDING_ORIENTATION)
224 placeObject(fx, fz, civEntities[0].Template, playerid, orientation);
225 for (var j = 1; j < civEntities.length; ++j)
227 var uAngle = orientation - PI * (2-j) / 2;
228 var count = (civEntities[j].Count !== undefined ? civEntities[j].Count : 1);
229 for (var numberofentities = 0; numberofentities < count; numberofentities++)
231 var ux = fx + uDist * cos(uAngle) + numberofentities * uSpace * cos(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * cos(uAngle + PI/2));
232 var uz = fz + uDist * sin(uAngle) + numberofentities * uSpace * sin(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * sin(uAngle + PI/2));
233 placeObject(ux, uz, civEntities[j].Template, playerid, uAngle);
238 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
239 // placeCivDefaultEntities
241 // Creates the default starting player entities depending on the players civ
242 // fx&fy: position of player base
243 // playerid: id of player
244 // kwargs: Takes some optional keyword arguments to tweek things
245 // 'iberWall': may be false, 'walls' (default) or 'towers'. Determines the defensive structures Iberians get as civ bonus
246 // 'orientation': angle of the main base building, default is BUILDING_ORIENTATION
248 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
249 function placeCivDefaultEntities(fx, fz, playerid, kwargs = {})
252 var iberWall = 'walls';
253 if (getMapSize() <= 128)
255 if ('iberWall' in kwargs)
256 iberWall = kwargs['iberWall'];
257 var orientation = BUILDING_ORIENTATION;
258 if ('orientation' in kwargs)
259 orientation = kwargs['orientation'];
260 // Place default civ starting entities
261 var civ = getCivCode(playerid-1);
262 var civEntities = getStartingEntities(playerid-1);
265 placeObject(fx, fz, civEntities[0].Template, playerid, orientation);
266 for (var j = 1; j < civEntities.length; ++j)
268 var uAngle = orientation - PI * (2-j) / 2;
269 var count = (civEntities[j].Count !== undefined ? civEntities[j].Count : 1);
270 for (var numberofentities = 0; numberofentities < count; numberofentities++)
272 var ux = fx + uDist * cos(uAngle) + numberofentities * uSpace * cos(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * cos(uAngle + PI/2));
273 var uz = fz + uDist * sin(uAngle) + numberofentities * uSpace * sin(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * sin(uAngle + PI/2));
274 placeObject(ux, uz, civEntities[j].Template, playerid, uAngle);
277 // Add defensive structiures for Iberians as their civ bonus
278 if (civ == 'iber' && iberWall != false)
280 if (iberWall == 'towers')
281 placePolygonalWall(fx, fz, 15, ['entry'], 'tower', civ, playerid, orientation, 7);
283 placeGenericFortress(fx, fz, 20/*radius*/, playerid);
287 function placeDefaultChicken(playerX, playerZ, tileClass, constraint = undefined, template = "gaia/fauna_chicken")
289 for (let j = 0; j < 2; ++j)
290 for (var tries = 0; tries < 10; ++tries)
292 let aAngle = randFloat(0, TWO_PI);
294 // Roman and ptolemian civic centers have a big footprint!
297 let aX = round(playerX + aDist * cos(aAngle));
298 let aZ = round(playerZ + aDist * sin(aAngle));
300 let group = new SimpleGroup(
301 [new SimpleObject(template, 5,5, 0,2)],
302 true, tileClass, aX, aZ
305 if (createObjectGroup(group, 0, constraint))
311 * Typically used for placing grass tufts around the civic centers.
313 function placeDefaultDecoratives(playerX, playerZ, template, tileclass, radius, constraint = undefined)
315 for (let i = 0; i < PI * radius * radius / 250; ++i)
317 let angle = randFloat(0, 2 * PI);
318 let dist = radius - randIntInclusive(5, 11);
322 [new SimpleObject(template, 2, 5, 0, 1, -PI/8, PI/8)],
325 Math.round(playerX + dist * Math.cos(angle)),
326 Math.round(playerZ + dist * Math.sin(angle))
331 function modifyTilesBasedOnHeight(minHeight, maxHeight, mode, func)
333 for (let qx = 0; qx < g_Map.size; ++qx)
334 for (let qz = 0; qz < g_Map.size; ++qz)
336 let height = g_Map.getHeight(qx, qz);
337 if (mode == 0 && height > minHeight && height < maxHeight ||
338 mode == 1 && height >= minHeight && height < maxHeight ||
339 mode == 2 && height > minHeight && height <= maxHeight ||
340 mode == 3 && height >= minHeight && height <= maxHeight)
345 function paintTerrainBasedOnHeight(minHeight, maxHeight, mode, terrain)
347 modifyTilesBasedOnHeight(minHeight, maxHeight, mode, (qx, qz) => {
348 placeTerrain(qx, qz, terrain);
352 function paintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileclass)
354 modifyTilesBasedOnHeight(minHeight, maxHeight, mode, (qx, qz) => {
355 addToClass(qx, qz, tileclass);
359 function unPaintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileclass)
361 modifyTilesBasedOnHeight(minHeight, maxHeight, mode, (qx, qz) => {
362 removeFromClass(qx, qz, tileclass);
366 /////////////////////////////////////////////////////////////////////////////////////////
369 // "get The Intended Point In A Direction Based On Height"
370 // gets the N'th point with a specific height in a line and returns it as a [x, y] array
371 // startPoint: [x, y] array defining the start point
372 // endPoint: [x, y] array defining the ending point
373 // heightRange: [min, max] array defining the range which the height of the intended point can be. includes both "min" and "max"
374 // step: how much tile units per turn should the search go. more value means faster but less accurate
375 // n: how many points to skip before ending the search. skips """n-1 points""".
377 ///////////////////////////////////////////////////////////////////////////////////////////
379 function getTIPIADBON(startPoint, endPoint, heightRange, step, n)
381 var stepX = step*(endPoint[0]-startPoint[0])/(sqrt((endPoint[0]-startPoint[0])*(endPoint[0]-startPoint[0]) + step*(endPoint[1]-startPoint[1])*(endPoint[1]-startPoint[1])));
382 var stepY = step*(endPoint[1]-startPoint[1])/(sqrt((endPoint[0]-startPoint[0])*(endPoint[0]-startPoint[0]) + step*(endPoint[1]-startPoint[1])*(endPoint[1]-startPoint[1])));
383 var y = startPoint[1];
385 for (var x = startPoint[0]; true; x += stepX)
387 if ((floor(x) < g_Map.size)||(floor(y) < g_Map.size))
389 if ((g_Map.getHeight(floor(x), floor(y)) <= heightRange[1])&&(g_Map.getHeight(floor(x), floor(y)) >= heightRange[0]))
399 if ((y > endPoint[1])&&(stepY>0))
401 if ((y < endPoint[1])&&(stepY<0))
403 if ((x > endPoint[1])&&(stepX>0))
405 if ((x < endPoint[1])&&(stepX<0))
411 /////////////////////////////////////////////////////////////////////////////////////////
414 // determines if two lines with the width "width" intersect or collide with each other
415 // x1, y1, x2, y2: determine the position of the first line
416 // x3, y3, x4, y4: determine the position of the second line
417 // width: determines the width of the lines
419 ///////////////////////////////////////////////////////////////////////////////////////////
421 function checkIfIntersect (x1, y1, x2, y2, x3, y3, x4, y4, width)
425 if (((x3 - x1) < width) || ((x4 - x2) < width))
430 var m = (y1 - y2) / (x1 - x2);
432 var m2 = sqrt(m * m + 1);
433 if ((Math.abs((y3 - x3 * m - b)/m2) < width) || (Math.abs((y4 - x4 * m - b)/m2) < width))
435 //neccessary for some situations.
438 if (((x1 - x3) < width) || ((x2 - x4) < width))
443 var m = (y3 - y4) / (x3 - x4);
445 var m2 = sqrt(m * m + 1);
446 if ((Math.abs((y1 - x1 * m - b)/m2) < width) || (Math.abs((y2 - x2 * m - b)/m2) < width))
451 var s = ((x1 - x2) * (y3 - y1) - (y1 - y2) * (x3 - x1)), p = ((x1 - x2) * (y4 - y1) - (y1 - y2) * (x4 - x1));
454 s = ((x3 - x4) * (y1 - y3) - (y3 - y4) * (x1 - x3));
455 p = ((x3 - x4) * (y2 - y3) - (y3 - y4) * (x2 - x3));
462 /////////////////////////////////////////////////////////////////////////////////////////
463 // distanceOfPointFromLine
465 // returns the distance of a point from a line
466 // x1, y1, x2, y2: determine the position of the line
467 // x3, y3: determine the position of the point
469 ///////////////////////////////////////////////////////////////////////////////////////////
471 function distanceOfPointFromLine (x1, y1, x2, y2, x3, y3)
475 return Math.abs(x3 - x1);
479 return Math.abs(y3 - y1);
483 var m = (y1 - y2) / (x1 - x2);
485 var m2 = sqrt(m * m + 1);
486 return Math.abs((y3 - x3 * m - b)/m2);
490 /////////////////////////////////////////////////////////////////////////////////////////
493 // creates a ramp from point (x1, y1) to (x2, y2).
494 // x1, y1, x2, y2: determine the position of the start and end of the ramp
495 // minHeight, maxHeight: determine the height levels of the start and end point
496 // width: determines the width of the ramp
497 // smoothLevel: determines the smooth level around the edges of the ramp
498 // mainTerrain: (Optional) determines the terrain texture for the ramp
499 // edgeTerrain: (Optional) determines the terrain texture for the edges
500 // tileclass: (Optional) adds the ramp to this tile class
502 ///////////////////////////////////////////////////////////////////////////////////////////
504 function createRamp (x1, y1, x2, y2, minHeight, maxHeight, width, smoothLevel, mainTerrain, edgeTerrain, tileclass)
506 var halfWidth = width / 2;
507 var mapSize = g_Map.size;
512 var y3 = y2 + halfWidth;
516 var m = (x1 - x2) / (y1 - y2);
518 var x3 = x2 + halfWidth;
519 var y3 = - m * x3 + b;
522 var minBoundX = (x1 <= x2 ? (x1 > halfWidth ? x1 - halfWidth : 0) : (x2 > halfWidth ? x2 - halfWidth : 0));
523 var maxBoundX = (x1 >= x2 ? (x1 < mapSize - halfWidth ? x1 + halfWidth : mapSize) : (x2 < mapSize - halfWidth ? x2 + halfWidth : mapSize));
524 var minBoundY = (y1 <= y2 ? (y1 > halfWidth ? y1 - halfWidth : 0) : (y2 > halfWidth ? y2 - halfWidth : 0));
525 var maxBoundY = (y1 >= y2 ? (x1 < mapSize - halfWidth ? y1 + halfWidth : mapSize) : (y2 < mapSize - halfWidth ? y2 + halfWidth : mapSize));
527 for (var x = minBoundX; x < maxBoundX; ++x)
529 for (var y = minBoundY; y < maxBoundY; ++y)
531 var lDist = distanceOfPointFromLine(x3, y3, x2, y2, x, y);
532 var sDist = distanceOfPointFromLine(x1, y1, x2, y2, x, y);
533 var rampLength = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
534 if (lDist <= rampLength && sDist <= halfWidth)
536 var h = ((rampLength - lDist) * maxHeight + lDist * minHeight) / rampLength;
537 if (sDist >= halfWidth - smoothLevel)
539 h = (h - minHeight) * (halfWidth - sDist) / smoothLevel + minHeight;
540 if (edgeTerrain !== undefined)
541 placeTerrain(x, y, edgeTerrain);
545 if (mainTerrain !== undefined)
546 placeTerrain(x, y, mainTerrain);
548 if (tileclass !== undefined)
549 addToClass(x, y, tileclass);
550 if((g_Map.getHeight(floor(x), floor(y)) < h) && (h <= maxHeight))
551 g_Map.setHeight(x, y, h);
557 /////////////////////////////////////////////////////////////////////////////////////////
560 // creates a mountain using a tecnique very similar to chain placer.
562 ///////////////////////////////////////////////////////////////////////////////////////////
564 function createMountain(maxHeight, minRadius, maxRadius, numCircles, constraint, x, z, terrain, tileclass, fcc, q)
566 fcc = (fcc !== undefined ? fcc : 0);
567 q = (q !== undefined ? q : []);
569 // checking for an array of constraints
570 if (constraint instanceof Array)
572 var constraintArray = constraint;
573 constraint = new AndConstraint(constraintArray);
576 // Preliminary bounds check
577 if (!g_Map.inMapBounds(x, z) || !constraint.allows(x, z))
582 var size = getMapSize();
583 var queueEmpty = (q.length ? false : true);
586 for (var i = 0; i < size; ++i)
589 for (var j = 0; j < size; ++j)
595 if (minRadius < 1) minRadius = 1;
596 if (minRadius > maxRadius) minRadius = maxRadius;
598 var edges = [[x, z]];
601 for (var i = 0; i < numCircles; ++i)
603 var badPoint = false;
604 var [cx, cz] = pickRandom(edges);
607 var radius = randIntInclusive(minRadius, maxRadius);
610 var radius = q.pop();
611 queueEmpty = (q.length ? false : true);
614 var sx = cx - radius, lx = cx + radius;
615 var sz = cz - radius, lz = cz + radius;
617 sx = (sx < 0 ? 0 : sx);
618 sz = (sz < 0 ? 0 : sz);
619 lx = (lx > size ? size : lx);
620 lz = (lz > size ? size : lz);
622 var radius2 = radius * radius;
623 var dx, dz, distance2;
625 //log (uneval([sx, sz, lx, lz]));
627 for (var ix = sx; ix <= lx; ++ix)
629 for (var iz = sz; iz <= lz; ++ iz)
633 distance2 = dx * dx + dz * dz;
634 if (dx * dx + dz * dz <= radius2)
636 if (g_Map.inMapBounds(ix, iz))
638 if (!constraint.allows(ix, iz))
644 var state = gotRet[ix][iz];
653 var s = edges.splice(state, 1);
657 var edgesLength = edges.length;
658 for (var k = state; k < edges.length; ++k)
660 --gotRet[edges[k][0]][edges[k][1]];
673 circles.push([cx, cz, radius]);
675 for (var ix = sx; ix <= lx; ++ix)
677 for (var iz = sz; iz <= lz; ++ iz)
680 if ((x - ix) > fcc || (ix - x) > fcc || (z - iz) > fcc || (iz - z) > fcc)
683 if (gotRet[ix][iz] == -2)
687 if (gotRet[ix-1][iz] == -1)
689 edges.push([ix, iz]);
690 gotRet[ix][iz] = edges.length - 1;
696 if (gotRet[ix][iz-1] == -1)
698 edges.push([ix, iz]);
699 gotRet[ix][iz] = edges.length - 1;
705 if (gotRet[ix+1][iz] == -1)
707 edges.push([ix, iz]);
708 gotRet[ix][iz] = edges.length - 1;
714 if (gotRet[ix][iz+1] == -1)
716 edges.push([ix, iz]);
717 gotRet[ix][iz] = edges.length - 1;
726 var numFinalCircles = circles.length;
728 for (var i = 0; i < numFinalCircles; ++i)
730 var point = circles[i];
731 var cx = point[0], cz = point[1], radius = point[2];
733 var sx = cx - radius, lx = cx + radius;
734 var sz = cz - radius, lz = cz + radius;
736 sx = (sx < 0 ? 0 : sx);
737 sz = (sz < 0 ? 0 : sz);
738 lx = (lx > size ? size : lx);
739 lz = (lz > size ? size : lz);
741 var radius2 = radius * radius;
742 var dx, dz, distance2;
744 var clumpHeight = radius / maxRadius * maxHeight * randFloat(0.8, 1.2);
746 for (var ix = sx; ix <= lx; ++ix)
748 for (var iz = sz; iz <= lz; ++ iz)
752 distance2 = dx * dx + dz * dz;
754 var newHeight = Math.round((Math.sin(PI * (2 * ((radius - Math.sqrt(distance2)) / radius) / 3 - 1/6)) + 0.5) * 2/3 * clumpHeight) + randIntInclusive(0, 2);
756 if (dx * dx + dz * dz <= radius2)
758 if (g_Map.getHeight(ix, iz) < newHeight)
759 g_Map.setHeight(ix, iz, newHeight);
760 else if (g_Map.getHeight(ix, iz) >= newHeight && g_Map.getHeight(ix, iz) < newHeight + 4)
761 g_Map.setHeight(ix, iz, newHeight + 4);
762 if (terrain !== undefined)
763 placeTerrain(ix, iz, terrain);
764 if (tileclass !== undefined)
765 addToClass(ix, iz, tileclass);
773 * Generates a volcano mountain. Smoke and lava are optional.
775 * @param {number} fx - Horizontal coordinate of the center.
776 * @param {number} fz - Horizontal coordinate of the center.
777 * @param {number} tileClass - Painted onto every tile that is occupied by the volcano.
778 * @param {string} terrainTexture - The texture painted onto the volcano hill.
779 * @param {array} lavaTextures - Three different textures for the interior, from the outside to the inside.
780 * @param {boolean} smoke - Whether to place smoke particles.
781 * @param {number} elevationType - Elevation painter type, ELEVATION_SET = absolute or ELEVATION_MODIFY = relative.
783 function createVolcano(fx, fz, tileClass, terrainTexture, lavaTextures, smoke, elevationType)
785 log("Creating volcano");
787 let ix = Math.round(fractionToTiles(fx));
788 let iz = Math.round(fractionToTiles(fz));
790 let baseSize = mapArea / scaleByMapSize(1, 8);
793 let smoothness = 0.05;
794 let failFraction = 100;
797 let clLava = createTileClass();
803 "tileClass": tileClass
808 "tileClass": createTileClass()
813 "tileClass": createTileClass()
818 "tileClass": createTileClass()
824 "painter": lavaTextures && new LayeredPainter([terrainTexture, ...lavaTextures], [1, 1, 1]),
829 for (let i = 0; i < layers.length; ++i)
831 new ClumpPlacer(baseSize * layers[i].clumps, coherence, smoothness, failFraction, ix, iz),
833 layers[i].painter || new LayeredPainter([terrainTexture, terrainTexture], [3]),
834 new SmoothElevationPainter(elevationType, layers[i].elevation, layers[i].steepness || steepness),
835 paintClass(layers[i].tileClass)
837 i == 0 ? null : stayClasses(layers[i - 1].tileClass, 1));
841 let num = Math.floor(baseSize * 0.002);
844 [new SimpleObject("actor|particle/smoke.xml", num, num, 0, 7)],
850 stayClasses(tileClass, 1));