Ticket #3900: seperate_rm-team.patch

File seperate_rm-team.patch, 52.6 KB (added by FeXoR, 8 years ago)

Patch against r18022

  • binaries/data/mods/public/maps/random/ambush.js

     
    11RMS.LoadLibrary("rmgen");
     2RMS.LoadLibrary("rm-team");
    23
    34InitMap();
    45
  • binaries/data/mods/public/maps/random/empire.js

     
    11RMS.LoadLibrary("rmgen");
     2RMS.LoadLibrary("rm-team");
     3
    24InitMap();
    35
    46randomizeBiome();
  • binaries/data/mods/public/maps/random/frontier.js

     
    11RMS.LoadLibrary("rmgen");
     2RMS.LoadLibrary("rm-team");
     3
    24InitMap();
    35
    46randomizeBiome();
  • binaries/data/mods/public/maps/random/hells_pass.js

     
    11RMS.LoadLibrary("rmgen");
     2RMS.LoadLibrary("rm-team");
     3
    24InitMap();
    35
    46randomizeBiome();
  • binaries/data/mods/public/maps/random/lions_den.js

     
    11RMS.LoadLibrary("rmgen");
     2RMS.LoadLibrary("rm-team");
     3
    24InitMap();
    35
    46randomizeBiome();
  • binaries/data/mods/public/maps/random/rm-team/gaia.js

     
     1const g_Props = {
     2    "barrels": "actor|props/special/eyecandy/barrels_buried.xml",
     3    "crate": "actor|props/special/eyecandy/crate_a.xml",
     4    "cart": "actor|props/special/eyecandy/handcart_1_broken.xml",
     5    "well": "actor|props/special/eyecandy/well_1_c.xml",
     6    "skeleton": "actor|props/special/eyecandy/skeleton.xml",
     7};
     8
     9const g_DefaultDeviation = 0.1;
     10
     11/**
     12 * Create bluffs, i.e. a slope hill reachable from ground level.
     13 * Fill it with wood, mines, animals and decoratives.
     14 *
     15 * @param {Array} constraint - where to place them
     16 * @param {number} size - size of the bluffs (1.2 would be 120% of normal)
     17 * @param {number} deviation - degree of deviation from the defined size (0.2 would be 20% plus/minus)
     18 * @param {number} fill - size of map to fill (1.5 would be 150% of normal)
     19 */
     20function addBluffs(constraint, size, deviation, fill)
     21{
     22    deviation = deviation || g_DefaultDeviation;
     23    size = size || 1;
     24    fill = fill || 1;
     25
     26    var constrastTerrain = g_Terrains.tier2Terrain;
     27
     28    if (g_MapInfo.biome == g_BiomeTropic)
     29        constrastTerrain = g_Terrains.dirt;
     30
     31    if (g_MapInfo.biome == g_BiomeAutumn)
     32        constrastTerrain = g_Terrains.tier3Terrain;
     33
     34    var count = fill * scaleByMapSize(15, 15);
     35    var minSize = scaleByMapSize(5, 5);
     36    var maxSize = scaleByMapSize(7, 7);
     37    var elevation = 30;
     38    var spread = scaleByMapSize(100, 100);
     39
     40    for (var i = 0; i < count; ++i)
     41    {
     42        var offset = getRandomDeviation(size, deviation);
     43
     44        var pMinSize = Math.floor(minSize * offset);
     45        var pMaxSize = Math.floor(maxSize * offset);
     46        var pSpread = Math.floor(spread * offset);
     47        var pElevation = Math.floor(elevation * offset);
     48
     49        var placer = new ChainPlacer(pMinSize, pMaxSize, pSpread, 0.5);
     50        var terrainPainter = new LayeredPainter([g_Terrains.cliff, g_Terrains.mainTerrain, constrastTerrain], [2, 3]);
     51        var elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, pElevation, 2);
     52        var rendered = createAreas(placer, [terrainPainter, elevationPainter, paintClass(g_TileClasses.bluff)], constraint, 1);
     53
     54        // Find the bounding box of the bluff
     55        if (rendered[0] === undefined)
     56            continue;
     57
     58        var points = rendered[0].points;
     59
     60        var corners = findCorners(points);
     61
     62        // Seed an array the size of the bounding box
     63        var bb = createBoundingBox(points, corners);
     64
     65        // Get a random starting position for the baseline and the endline
     66        var angle = randInt(4);
     67        var opAngle = angle - 2;
     68        if (angle < 2)
     69            opAngle = angle + 2;
     70
     71        // Find the edges of the bluff
     72        var baseLine;
     73        var endLine;
     74
     75        // If we can't access the bluff, try different angles
     76        var retries = 0;
     77        var bluffCat = 2;
     78        while (bluffCat != 0 && retries < 5)
     79        {
     80            baseLine = findClearLine(bb, corners, angle);
     81            endLine = findClearLine(bb, corners, opAngle);
     82
     83            bluffCat = unreachableBluff(bb, corners, baseLine, endLine);
     84            ++angle;
     85            if (angle > 3)
     86                angle = 0;
     87
     88            opAngle = angle - 2;
     89            if (angle < 2)
     90                opAngle = angle + 2;
     91
     92            ++retries;
     93        }
     94
     95        // Inaccessible, turn it into a plateau
     96        if (bluffCat > 0)
     97        {
     98            removeBluff(points);
     99            continue;
     100        }
     101
     102        // Create an entrance area by using a small margin
     103        var margin = 0.08;
     104        var ground = createTerrain(g_Terrains.mainTerrain);
     105        var slopeLength = (1 - margin) * getDistance(baseLine.midX, baseLine.midZ, endLine.midX, endLine.midZ);
     106
     107        // Adjust the height of each point in the bluff
     108        for (var p = 0; p < points.length; ++p)
     109        {
     110            var pt = points[p];
     111            var dist = distanceOfPointFromLine(baseLine.x1, baseLine.z1, baseLine.x2, baseLine.z2, pt.x, pt.z);
     112
     113            var curHeight = g_Map.getHeight(pt.x, pt.z);
     114            var newHeight = curHeight - curHeight * (dist / slopeLength) - 2;
     115
     116            newHeight = Math.max(newHeight, endLine.height);
     117
     118            if (newHeight <= endLine.height + 2 && g_Map.validT(pt.x, pt.z) && g_Map.getTexture(pt.x, pt.z).indexOf('cliff') > -1)
     119                ground.place(pt.x, pt.z);
     120
     121            g_Map.setHeight(pt.x, pt.z, newHeight);
     122        }
     123
     124        // Smooth out the ground around the bluff
     125        fadeToGround(bb, corners.minX, corners.minZ, endLine.height);
     126    }
     127
     128    addElements([
     129        {
     130            "func": addHills,
     131            "avoid": [
     132                g_TileClasses.hill, 3,
     133                g_TileClasses.player, 20,
     134                g_TileClasses.valley, 2,
     135                g_TileClasses.water, 2
     136            ],
     137            "stay": [g_TileClasses.bluff, 3],
     138            "sizes": g_AllSizes,
     139            "mixes": g_AllMixes,
     140            "amounts": g_AllAmounts
     141        }
     142    ]);
     143
     144    addElements([
     145        {
     146            "func": addLayeredPatches,
     147            "avoid": [
     148                g_TileClasses.dirt, 5,
     149                g_TileClasses.forest, 2,
     150                g_TileClasses.mountain, 2,
     151                g_TileClasses.player, 12,
     152                g_TileClasses.water, 3
     153            ],
     154            "stay": [g_TileClasses.bluff, 5],
     155            "sizes": ["normal"],
     156            "mixes": ["normal"],
     157            "amounts": ["normal"]
     158        }
     159    ]);
     160
     161    addElements([
     162        {
     163            "func": addDecoration,
     164            "avoid": [
     165                g_TileClasses.forest, 2,
     166                g_TileClasses.player, 12,
     167                g_TileClasses.water, 3
     168            ],
     169            "stay": [g_TileClasses.bluff, 5],
     170            "sizes": ["normal"],
     171            "mixes": ["normal"],
     172            "amounts": ["normal"]
     173        }
     174    ]);
     175
     176    addElements([
     177        {
     178            "func": addProps,
     179            "avoid": [
     180                g_TileClasses.forest, 2,
     181                g_TileClasses.player, 12,
     182                g_TileClasses.prop, 40,
     183                g_TileClasses.water, 3
     184            ],
     185            "stay": [
     186                g_TileClasses.bluff, 7,
     187                g_TileClasses.mountain, 7
     188            ],
     189            "sizes": ["normal"],
     190            "mixes": ["normal"],
     191            "amounts": ["scarce"]
     192        }
     193    ]);
     194
     195    addElements(shuffleArray([
     196        {
     197            "func": addForests,
     198            "avoid": [
     199                g_TileClasses.berries, 5,
     200                g_TileClasses.forest, 18,
     201                g_TileClasses.metal, 5,
     202                g_TileClasses.mountain, 5,
     203                g_TileClasses.player, 20,
     204                g_TileClasses.rock, 5,
     205                g_TileClasses.water, 2
     206            ],
     207            "stay": [g_TileClasses.bluff, 6],
     208            "sizes": g_AllSizes,
     209            "mixes": g_AllMixes,
     210            "amounts": ["normal", "many", "tons"]
     211        },
     212        {
     213            "func": addMetal,
     214            "avoid": [
     215                g_TileClasses.berries, 5,
     216                g_TileClasses.forest, 5,
     217                g_TileClasses.mountain, 2,
     218                g_TileClasses.player, 50,
     219                g_TileClasses.rock, 15,
     220                g_TileClasses.metal, 40,
     221                g_TileClasses.water, 3
     222            ],
     223            "stay": [g_TileClasses.bluff, 6],
     224            "sizes": ["normal"],
     225            "mixes": ["same"],
     226            "amounts": ["normal"]
     227        },
     228        {
     229            "func": addStone,
     230            "avoid": [
     231                g_TileClasses.berries, 5,
     232                g_TileClasses.forest, 5,
     233                g_TileClasses.mountain, 2,
     234                g_TileClasses.player, 50,
     235                g_TileClasses.rock, 40,
     236                g_TileClasses.metal, 15,
     237                g_TileClasses.water, 3
     238            ],
     239            "stay": [g_TileClasses.bluff, 6],
     240            "sizes": ["normal"],
     241            "mixes": ["same"],
     242            "amounts": ["normal"]
     243        }
     244    ]));
     245
     246    let savanna = g_MapInfo.biome == g_BiomeSavanna;
     247    addElements(shuffleArray([
     248        {
     249            "func": addStragglerTrees,
     250            "avoid": [
     251                g_TileClasses.berries, 5,
     252                g_TileClasses.forest, 10,
     253                g_TileClasses.metal, 5,
     254                g_TileClasses.mountain, 1,
     255                g_TileClasses.player, 12,
     256                g_TileClasses.rock, 5,
     257                g_TileClasses.water, 5
     258             ],
     259            "stay": [g_TileClasses.bluff, 6],
     260            "sizes": savanna ? ["big"] : g_AllSizes,
     261            "mixes": savanna ? ["varied"] : g_AllMixes,
     262            "amounts": savanna ? ["tons"] : ["normal", "many", "tons"]
     263        },
     264        {
     265            "func": addAnimals,
     266            "avoid": [
     267                g_TileClasses.animals, 20,
     268                g_TileClasses.forest, 5,
     269                g_TileClasses.mountain, 1,
     270                g_TileClasses.player, 20,
     271                g_TileClasses.rock, 5,
     272                g_TileClasses.metal, 5,
     273                g_TileClasses.water, 3
     274             ],
     275            "stay": [g_TileClasses.bluff, 6],
     276            "sizes": g_AllSizes,
     277            "mixes": g_AllMixes,
     278            "amounts": ["normal", "many", "tons"]
     279        },
     280        {
     281            "func": addBerries,
     282            "avoid": [
     283                g_TileClasses.berries, 50,
     284                g_TileClasses.forest, 5,
     285                g_TileClasses.metal, 10,
     286                g_TileClasses.mountain, 2,
     287                g_TileClasses.player, 20,
     288                g_TileClasses.rock, 10,
     289                g_TileClasses.water, 3
     290            ],
     291            "stay": [g_TileClasses.bluff, 6],
     292            "sizes": g_AllSizes,
     293            "mixes": g_AllMixes,
     294            "amounts": ["normal", "many", "tons"]
     295        }
     296    ]));
     297}
     298
     299/**
     300 * Add grass, rocks and bushes.
     301 */
     302function addDecoration(constraint, size, deviation, fill)
     303{
     304    deviation = deviation || g_DefaultDeviation;
     305    size = size || 1;
     306    fill = fill || 1;
     307
     308    var offset = getRandomDeviation(size, deviation);
     309    var decorations = [
     310        [
     311            new SimpleObject(g_Decoratives.rockMedium, 1 * offset, 3 * offset, 0, 1 * offset)
     312        ],
     313        [
     314            new SimpleObject(g_Decoratives.rockLarge, 1 * offset, 2 * offset, 0, 1 * offset),
     315            new SimpleObject(g_Decoratives.rockMedium, 1 * offset, 3 * offset, 0, 2 * offset)
     316        ],
     317        [
     318            new SimpleObject(g_Decoratives.grassShort, 1 * offset, 2 * offset, 0, 1 * offset, -PI / 8, PI / 8)
     319        ],
     320        [
     321            new SimpleObject(g_Decoratives.grass, 2 * offset, 4 * offset, 0, 1.8 * offset, -PI / 8, PI / 8),
     322            new SimpleObject(g_Decoratives.grassShort, 3 * offset, 6 * offset, 1.2 * offset, 2.5 * offset, -PI / 8, PI / 8)
     323        ],
     324        [
     325            new SimpleObject(g_Decoratives.bushMedium, 1 * offset, 2 * offset, 0, 2 * offset),
     326            new SimpleObject(g_Decoratives.bushSmall, 2 * offset, 4 * offset, 0, 2 * offset)
     327        ]
     328    ];
     329
     330    var baseCount = 1;
     331    if (g_MapInfo.biome == g_BiomeTropic)
     332        baseCount = 8;
     333
     334    var counts = [
     335        scaleByMapSize(16, 262),
     336        scaleByMapSize(8, 131),
     337        baseCount * scaleByMapSize(13, 200),
     338        baseCount * scaleByMapSize(13, 200),
     339        baseCount * scaleByMapSize(13, 200)
     340    ];
     341
     342    for (var i = 0; i < decorations.length; ++i)
     343    {
     344        var decorCount = Math.floor(counts[i] * fill);
     345        var group = new SimpleGroup(decorations[i], true);
     346        createObjectGroups(group, 0, constraint, decorCount, 5);
     347    }
     348}
     349
     350/**
     351 * Create varying elevations.
     352 *
     353 * @param {Array} constraint - avoid/stay-classes
     354 *
     355 * @param {Object} el - the element to be rendered, for example:
     356 *  "class": g_TileClasses.hill,
     357 *  "painter": [g_Terrains.mainTerrain, g_Terrains.mainTerrain],
     358 *  "size": 1,
     359 *  "deviation": 0.2,
     360 *  "fill": 1,
     361 *  "count": scaleByMapSize(8, 8),
     362 *  "minSize": Math.floor(scaleByMapSize(5, 5)),
     363 *  "maxSize": Math.floor(scaleByMapSize(8, 8)),
     364 *  "spread": Math.floor(scaleByMapSize(20, 20)),
     365 *  "minElevation": 6,
     366 *  "maxElevation": 12,
     367 *  "steepness": 1.5
     368 */
     369
     370function addElevation(constraint, el)
     371{
     372    var deviation = el.deviation || g_DefaultDeviation;
     373    var size = el.size || 1;
     374    var fill = el.fill || 1;
     375
     376    var count = fill * el.count;
     377    var minSize = el.minSize;
     378    var maxSize = el.maxSize;
     379    var spread = el.spread;
     380
     381    var elType = ELEVATION_MODIFY;
     382    if (el.class == g_TileClasses.water)
     383        elType = ELEVATION_SET;
     384
     385    var widths = [];
     386
     387    // Allow for shore and cliff rendering
     388    for (var s = el.painter.length; s > 2; --s)
     389        widths.push(1);
     390
     391    for (var i = 0; i < count; ++i)
     392    {
     393        var elevation = el.minElevation + randInt(el.maxElevation - el.minElevation);
     394        var smooth = Math.floor(elevation / el.steepness);
     395
     396        var offset = getRandomDeviation(size, el.deviation);
     397        var pMinSize = Math.floor(minSize * offset);
     398        var pMaxSize = Math.floor(maxSize * offset);
     399        var pSpread = Math.floor(spread * offset);
     400        var pSmooth = Math.abs(Math.floor(smooth * offset));
     401        var pElevation = Math.floor(elevation * offset);
     402
     403        pElevation = Math.max(el.minElevation, Math.min(pElevation, el.maxElevation));
     404
     405        pMinSize = Math.min(pMinSize, pMaxSize);
     406        pMaxSize = Math.min(pMaxSize, el.maxSize);
     407        pMinSize = Math.max(pMaxSize, el.minSize);
     408
     409        pSmooth = Math.max(pSmooth, 1);
     410
     411        var pWidths = widths.concat(pSmooth);
     412
     413        var placer = new ChainPlacer(pMinSize, pMaxSize, pSpread, 0.5);
     414        var terrainPainter = new LayeredPainter(el.painter, [pWidths]);
     415        var elevationPainter = new SmoothElevationPainter(elType, pElevation, pSmooth);
     416        createAreas(placer, [terrainPainter, elevationPainter, paintClass(el.class)], constraint, 1);
     417    }
     418}
     419
     420/**
     421 * Create rolling hills.
     422 */
     423function addHills(constraint, size, deviation, fill)
     424{
     425    addElevation(constraint, {
     426        "class": g_TileClasses.hill,
     427        "painter": [g_Terrains.mainTerrain, g_Terrains.mainTerrain],
     428        "size": size,
     429        "deviation": deviation,
     430        "fill": fill,
     431        "count": scaleByMapSize(8, 8),
     432        "minSize": Math.floor(scaleByMapSize(5, 5)),
     433        "maxSize": Math.floor(scaleByMapSize(8, 8)),
     434        "spread": Math.floor(scaleByMapSize(20, 20)),
     435        "minElevation": 6,
     436        "maxElevation": 12,
     437        "steepness": 1.5
     438    });
     439}
     440
     441/**
     442 * Create random lakes with fish in it.
     443 */
     444function addLakes(constraint, size, deviation, fill)
     445{
     446    var lakeTile = g_Terrains.water;
     447
     448    if (g_MapInfo.biome == g_BiomeTemperate || g_MapInfo.biome == g_BiomeTropic)
     449        lakeTile = g_Terrains.dirt;
     450
     451    if (g_MapInfo.biome == g_BiomeMediterranean)
     452        lakeTile = g_Terrains.tier2Terrain;
     453
     454    if (g_MapInfo.biome == g_BiomeAutumn)
     455        lakeTile = g_Terrains.shore;
     456
     457    addElevation(constraint, {
     458        "class": g_TileClasses.water,
     459        "painter": [lakeTile, lakeTile],
     460        "size": size,
     461        "deviation": deviation,
     462        "fill": fill,
     463        "count": scaleByMapSize(6, 6),
     464        "minSize": Math.floor(scaleByMapSize(7, 7)),
     465        "maxSize": Math.floor(scaleByMapSize(9, 9)),
     466        "spread": Math.floor(scaleByMapSize(70, 70)),
     467        "minElevation": -15,
     468        "maxElevation": -2,
     469        "steepness": 1.5
     470    });
     471
     472    addElements([
     473        {
     474            "func": addFish,
     475            "avoid": [
     476                g_TileClasses.fish, 12,
     477                g_TileClasses.hill, 8,
     478                g_TileClasses.mountain, 8,
     479                g_TileClasses.player, 8
     480            ],
     481            "stay": [g_TileClasses.water, 7],
     482            "sizes": g_AllSizes,
     483            "mixes": g_AllMixes,
     484            "amounts": ["normal", "many", "tons"]
     485        }
     486    ]);
     487
     488    var group = new SimpleGroup([new SimpleObject(g_Decoratives.rockMedium, 1, 3, 1, 3)], true, g_TileClasses.dirt);
     489    createObjectGroups(group, 0, [stayClasses(g_TileClasses.water, 1), borderClasses(g_TileClasses.water, 4, 3)], 1000, 100);
     490
     491    group = new SimpleGroup([new SimpleObject(g_Decoratives.reeds, 10, 15, 1, 3), new SimpleObject(g_Decoratives.rockMedium, 1, 3, 1, 3)], true, g_TileClasses.dirt);
     492    createObjectGroups(group, 0, [stayClasses(g_TileClasses.water, 2), borderClasses(g_TileClasses.water, 4, 3)], 1000, 100);
     493}
     494
     495/**
     496 * Universal function to create layered patches.
     497 */
     498function addLayeredPatches(constraint, size, deviation, fill)
     499{
     500    deviation = deviation || g_DefaultDeviation;
     501    size = size || 1;
     502    fill = fill || 1;
     503
     504    var minRadius = 1;
     505    var maxRadius = Math.floor(scaleByMapSize(3, 5));
     506    var count = fill * scaleByMapSize(15, 45);
     507
     508    var sizes = [
     509        scaleByMapSize(3, 6),
     510        scaleByMapSize(5, 10),
     511        scaleByMapSize(8, 21)
     512    ];
     513
     514    for (var i = 0; i < sizes.length; ++i)
     515    {
     516        var offset = getRandomDeviation(size, deviation);
     517        var patchMinRadius = Math.floor(minRadius * offset);
     518        var patchMaxRadius = Math.floor(maxRadius * offset);
     519        var patchSize = Math.floor(sizes[i] * offset);
     520        var patchCount = count * offset;
     521
     522        if (patchMinRadius > patchMaxRadius)
     523            patchMinRadius = patchMaxRadius;
     524
     525        var placer = new ChainPlacer(patchMinRadius, patchMaxRadius, patchSize, 0.5);
     526        var painter = new LayeredPainter(
     527            [
     528                [g_Terrains.mainTerrain, g_Terrains.tier1Terrain],
     529                [g_Terrains.tier1Terrain, g_Terrains.tier2Terrain],
     530                [g_Terrains.tier2Terrain, g_Terrains.tier3Terrain],
     531                [g_Terrains.tier4Terrain]
     532            ],
     533            [1, 1] // widths
     534        );
     535        createAreas(placer, [painter, paintClass(g_TileClasses.dirt)], constraint, patchCount);
     536    }
     537}
     538
     539/**
     540 * Create steep mountains.
     541 */
     542function addMountains(constraint, size, deviation, fill)
     543{
     544    addElevation(constraint, {
     545        "class": g_TileClasses.mountain,
     546        "painter": [g_Terrains.cliff, g_Terrains.hill],
     547        "size": size,
     548        "deviation": deviation,
     549        "fill": fill,
     550        "count": scaleByMapSize(8, 8),
     551        "minSize": Math.floor(scaleByMapSize(2, 2)),
     552        "maxSize": Math.floor(scaleByMapSize(4, 4)),
     553        "spread": Math.floor(scaleByMapSize(100, 100)),
     554        "minElevation": 100,
     555        "maxElevation": 120,
     556        "steepness": 4
     557    });
     558}
     559
     560/**
     561 * Create plateaus.
     562 */
     563function addPlateaus(constraint, size, deviation, fill)
     564{
     565    var plateauTile = g_Terrains.dirt;
     566
     567    if (g_MapInfo.biome == g_BiomeSnowy)
     568        plateauTile = g_Terrains.tier1Terrain;
     569
     570    if (g_MapInfo.biome == g_BiomeAlpine || g_MapInfo.biome == g_BiomeSavanna)
     571        plateauTile = g_Terrains.tier2Terrain;
     572
     573    if (g_MapInfo.biome == g_BiomeAutumn)
     574        plateauTile = g_Terrains.tier4Terrain;
     575
     576    addElevation(constraint, {
     577        "class": g_TileClasses.plateau,
     578        "painter": [g_Terrains.cliff, plateauTile],
     579        "size": size,
     580        "deviation": deviation,
     581        "fill": fill,
     582        "count": scaleByMapSize(15, 15),
     583        "minSize": Math.floor(scaleByMapSize(2, 2)),
     584        "maxSize": Math.floor(scaleByMapSize(4, 4)),
     585        "spread": Math.floor(scaleByMapSize(200, 200)),
     586        "minElevation": 20,
     587        "maxElevation": 30,
     588        "steepness": 8
     589    });
     590
     591    for (var i = 0; i < 40; ++i)
     592    {
     593        var placer = new ChainPlacer(3, 15, 1, 0.5);
     594        var terrainPainter = new LayeredPainter([plateauTile, plateauTile], [3]);
     595        var hillElevation = 4 + randInt(15);
     596        var elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, hillElevation, hillElevation - 2);
     597
     598        createAreas(
     599            placer,
     600            [
     601                terrainPainter,
     602                elevationPainter,
     603                paintClass(g_TileClasses.hill)
     604            ],
     605            [
     606                avoidClasses(g_TileClasses.hill, 7),
     607                stayClasses(g_TileClasses.plateau, 7)
     608            ],
     609            1
     610        );
     611    }
     612
     613    addElements([
     614        {
     615            "func": addDecoration,
     616            "avoid": [
     617                g_TileClasses.dirt, 15,
     618                g_TileClasses.forest, 2,
     619                g_TileClasses.player, 12,
     620                g_TileClasses.water, 3
     621            ],
     622            "stay": [g_TileClasses.plateau, 8],
     623            "sizes": ["normal"],
     624            "mixes": ["normal"],
     625            "amounts": ["tons"]
     626        },
     627        {
     628            "func": addProps,
     629            "avoid": [
     630                g_TileClasses.forest, 2,
     631                g_TileClasses.player, 12,
     632                g_TileClasses.prop, 40,
     633                g_TileClasses.water, 3
     634            ],
     635            "stay": [g_TileClasses.plateau, 8],
     636            "sizes": ["normal"],
     637            "mixes": ["normal"],
     638            "amounts": ["scarce"]
     639        }
     640    ]);
     641}
     642
     643/**
     644 * Place less usual decoratives like barrels or crates.
     645 */
     646function addProps(constraint, size, deviation, fill)
     647{
     648    deviation = deviation || g_DefaultDeviation;
     649    size = size || 1;
     650    fill = fill || 1;
     651
     652    var offset = getRandomDeviation(size, deviation);
     653
     654    var props = [
     655        [
     656            new SimpleObject(g_Props.skeleton, 1 * offset, 5 * offset, 0, 3 * offset + 2),
     657        ],
     658        [
     659            new SimpleObject(g_Props.barrels, 1 * offset, 2 * offset, 2, 3 * offset + 2),
     660            new SimpleObject(g_Props.cart, 0, 1 * offset, 5, 2.5 * offset + 5),
     661            new SimpleObject(g_Props.crate, 1 * offset, 2 * offset, 2, 2 * offset + 2),
     662            new SimpleObject(g_Props.well, 0, 1, 2, 2 * offset + 2)
     663        ]
     664    ];
     665
     666    var baseCount = 1;
     667
     668    var counts = [
     669        scaleByMapSize(16, 262),
     670        scaleByMapSize(8, 131),
     671        baseCount * scaleByMapSize(13, 200),
     672        baseCount * scaleByMapSize(13, 200),
     673        baseCount * scaleByMapSize(13, 200)
     674    ];
     675
     676    // Add small props
     677    for (var i = 0; i < props.length; ++i)
     678    {
     679        var propCount = Math.floor(counts[i] * fill);
     680        var group = new SimpleGroup(props[i], true);
     681        createObjectGroups(group, 0, constraint, propCount, 5);
     682    }
     683
     684    // Add decorative trees
     685    var trees = new SimpleObject(g_Decoratives.tree, 5 * offset, 30 * offset, 2, 3 * offset + 10);
     686    createObjectGroups(new SimpleGroup([trees], true), 0, constraint, counts[0] * 5 * fill, 5);
     687}
     688
     689/**
     690 * Create rivers.
     691 */
     692function addRivers(constraint, size, deviation, fill)
     693{
     694    deviation = deviation || g_DefaultDeviation;
     695    size = size || 1;
     696    fill = fill || 1;
     697
     698    var count = 5;
     699    var minSize = scaleByMapSize(15, 15);
     700    var maxSize = scaleByMapSize(15, 15);
     701    var elevation = -2;
     702    var spread = scaleByMapSize(5, 5);
     703
     704    for (var i = 0; i < count; ++i)
     705    {
     706        var offset = getRandomDeviation(size, deviation);
     707
     708        var startAngle = randFloat(0, 2 * PI);
     709        var endAngle = startAngle + randFloat(PI * 0.5, PI * 1.5);
     710
     711        var startX = g_MapInfo.centerOfMap + Math.floor(g_MapInfo.centerOfMap * Math.cos(startAngle));
     712        var startZ = g_MapInfo.centerOfMap + Math.floor(g_MapInfo.centerOfMap * Math.sin(startAngle));
     713
     714        var endX = g_MapInfo.centerOfMap + Math.floor(g_MapInfo.centerOfMap * Math.cos(endAngle));
     715        var endZ = g_MapInfo.centerOfMap + Math.floor(g_MapInfo.centerOfMap * Math.sin(endAngle));
     716
     717        var pMinSize = Math.floor(minSize * offset);
     718        var pMaxSize = Math.floor(maxSize * offset);
     719        var pSpread = Math.floor(spread * offset);
     720
     721        var placer = new PathPlacer(startX, startZ, endX, endZ, 12, 0.25, 1, 0.05, 0.3);
     722        var terrainPainter = new LayeredPainter([g_Terrains.water, g_Terrains.shore], [2]);
     723        var elevationPainter = new SmoothElevationPainter(ELEVATION_SET, elevation, 2);
     724        createArea(placer, [terrainPainter, elevationPainter, paintClass(g_TileClasses.water)], constraint);
     725    }
     726}
     727
     728/**
     729 * Create valleys.
     730 */
     731function addValleys(constraint, size, deviation, fill)
     732{
     733    if (g_MapInfo.mapHeight < 6)
     734        return;
     735
     736    var minElevation = (-1 * g_MapInfo.mapHeight) / (size * (1 + deviation)) + 1;
     737    if (minElevation < -1 * g_MapInfo.mapHeight)
     738        minElevation = -1 * g_MapInfo.mapHeight;
     739
     740    var valleySlope = g_Terrains.tier1Terrain;
     741    var valleyFloor = g_Terrains.tier4Terrain;
     742
     743    if (g_MapInfo.biome == g_BiomeDesert)
     744    {
     745        valleySlope = g_Terrains.tier3Terrain;
     746        valleyFloor = g_Terrains.dirt;
     747    }
     748
     749    if (g_MapInfo.biome == g_BiomeMediterranean)
     750    {
     751        valleySlope = g_Terrains.tier2Terrain;
     752        valleyFloor = g_Terrains.dirt;
     753    }
     754
     755    if (g_MapInfo.biome == g_BiomeAlpine || g_MapInfo.biome == g_BiomeSavanna)
     756        valleyFloor = g_Terrains.tier2Terrain;
     757
     758    if (g_MapInfo.biome == g_BiomeTropic)
     759        valleySlope = g_Terrains.dirt;
     760
     761    if (g_MapInfo.biome == g_BiomeAutumn)
     762        valleyFloor = g_Terrains.tier3Terrain;
     763
     764    addElevation(constraint, {
     765        "class": g_TileClasses.valley,
     766        "painter": [valleySlope, valleyFloor],
     767        "size": size,
     768        "deviation": deviation,
     769        "fill": fill,
     770        "count": scaleByMapSize(8, 8),
     771        "minSize": Math.floor(scaleByMapSize(5, 5)),
     772        "maxSize": Math.floor(scaleByMapSize(8, 8)),
     773        "spread": Math.floor(scaleByMapSize(30, 30)),
     774        "minElevation": minElevation,
     775        "maxElevation": -2,
     776        "steepness": 4
     777    });
     778}
     779
     780/**
     781 * Create huntable animals.
     782 */
     783function addAnimals(constraint, size, deviation, fill)
     784{
     785    deviation = deviation || g_DefaultDeviation;
     786    size = size || 1;
     787    fill = fill || 1;
     788
     789    var groupOffset = getRandomDeviation(size, deviation);
     790
     791    var animals = [
     792        [new SimpleObject(g_Gaia.mainHuntableAnimal, 5 * groupOffset, 7 * groupOffset, 0, 4 * groupOffset)],
     793        [new SimpleObject(g_Gaia.secondaryHuntableAnimal, 2 * groupOffset, 3 * groupOffset, 0, 2 * groupOffset)]
     794    ];
     795
     796    var counts = [scaleByMapSize(30, 30) * fill, scaleByMapSize(30, 30) * fill];
     797
     798    for (var i = 0; i < animals.length; ++i)
     799    {
     800        var group = new SimpleGroup(animals[i], true, g_TileClasses.animals);
     801        createObjectGroups(group, 0, constraint, Math.floor(counts[i]), 50);
     802    }
     803}
     804
     805/**
     806 * Create fruits.
     807 */
     808function addBerries(constraint, size, deviation, fill)
     809{
     810    deviation = deviation || g_DefaultDeviation;
     811    size = size || 1;
     812    fill = fill || 1;
     813
     814    var groupOffset = getRandomDeviation(size, deviation);
     815
     816    var count = scaleByMapSize(50, 50) * fill;
     817    var berries = [[new SimpleObject(g_Gaia.fruitBush, 5 * groupOffset, 5 * groupOffset, 0, 3 * groupOffset)]];
     818
     819    for (var i = 0; i < berries.length; ++i)
     820    {
     821        var group = new SimpleGroup(berries[i], true, g_TileClasses.berries);
     822        createObjectGroups(group, 0, constraint, Math.floor(count), 40);
     823    }
     824}
     825
     826/**
     827 * Create fish.
     828 */
     829function addFish(constraint, size, deviation, fill)
     830{
     831    deviation = deviation || g_DefaultDeviation;
     832    size = size || 1;
     833    fill = fill || 1;
     834
     835    var groupOffset = getRandomDeviation(size, deviation);
     836
     837    var fish = [
     838        [new SimpleObject(g_Gaia.fish, 1 * groupOffset, 2 * groupOffset, 0, 2 * groupOffset)],
     839        [new SimpleObject(g_Gaia.fish, 2 * groupOffset, 4 * groupOffset, 10 * groupOffset, 20 * groupOffset)]
     840    ];
     841
     842    var counts = [scaleByMapSize(40, 40) * fill, scaleByMapSize(40, 40) * fill];
     843
     844    for (var i = 0; i < fish.length; ++i)
     845    {
     846        var group = new SimpleGroup(fish[i], true, g_TileClasses.fish);
     847        createObjectGroups(group, 0, constraint, floor(counts[i]), 50);
     848    }
     849}
     850
     851/**
     852 * Create dense forests.
     853 */
     854function addForests(constraint, size, deviation, fill)
     855{
     856    deviation = deviation || g_DefaultDeviation;
     857    size = size || 1;
     858    fill = fill || 1;
     859
     860    // No forests for the african biome
     861    if (g_MapInfo.biome == g_BiomeSavanna)
     862        return;
     863
     864    var types = [
     865        [
     866            [g_Terrains.forestFloor2, g_Terrains.mainTerrain, g_Forests.forest1],
     867            [g_Terrains.forestFloor2, g_Forests.forest1]
     868        ],
     869        [
     870            [g_Terrains.forestFloor2, g_Terrains.mainTerrain, g_Forests.forest2],
     871            [g_Terrains.forestFloor1, g_Forests.forest2]],
     872        [
     873            [g_Terrains.forestFloor1, g_Terrains.mainTerrain, g_Forests.forest1],
     874            [g_Terrains.forestFloor2, g_Forests.forest1]],
     875        [
     876            [g_Terrains.forestFloor1, g_Terrains.mainTerrain, g_Forests.forest2],
     877            [g_Terrains.forestFloor1, g_Forests.forest2]
     878        ]
     879    ];
     880
     881    for (var i = 0; i < types.length; ++i)
     882    {
     883        var offset = getRandomDeviation(size, deviation);
     884        var minSize = floor(scaleByMapSize(3, 5) * offset);
     885        var maxSize = Math.floor(scaleByMapSize(50, 50) * offset);
     886        var forestCount = scaleByMapSize(10, 10) * fill;
     887
     888        var placer = new ChainPlacer(1, minSize, maxSize, 0.5);
     889        var painter = new LayeredPainter(types[i], [2]);
     890        createAreas(placer, [painter, paintClass(g_TileClasses.forest)], constraint, forestCount);
     891    }
     892}
     893
     894/**
     895 * Create metal mines.
     896 */
     897function addMetal(constraint, size, deviation, fill)
     898{
     899    deviation = deviation || g_DefaultDeviation;
     900    size = size || 1;
     901    fill = fill || 1;
     902
     903    var offset = getRandomDeviation(size, deviation);
     904    var count = 1 + scaleByMapSize(20, 20) * fill;
     905    var mines = [[new SimpleObject(g_Gaia.metalLarge, 1 * offset, 1 * offset, 0, 4 * offset)]];
     906
     907    for (var i = 0; i < mines.length; ++i)
     908    {
     909        var group = new SimpleGroup(mines[i], true, g_TileClasses.metal);
     910        createObjectGroups(group, 0, constraint, count, 100);
     911    }
     912}
     913
     914/**
     915 * Create stone mines.
     916 */
     917function addStone(constraint, size, deviation, fill)
     918{
     919    deviation = deviation || g_DefaultDeviation;
     920    size = size || 1;
     921    fill = fill || 1;
     922
     923    var offset = getRandomDeviation(size, deviation);
     924    var count = 1 + scaleByMapSize(20, 20) * fill;
     925    var mines = [
     926        [
     927            new SimpleObject(g_Gaia.stoneSmall, 0, 2 * offset, 0, 4 * offset),
     928            new SimpleObject(g_Gaia.stoneLarge, 1 * offset, 1 * offset, 0, 4 * offset)
     929        ],
     930        [
     931            new SimpleObject(g_Gaia.stoneSmall, 2 * offset, 5 * offset, 1 * offset, 3 * offset)
     932        ]
     933    ];
     934
     935    for (var i = 0; i < mines.length; ++i)
     936    {
     937        var group = new SimpleGroup(mines[i], true, g_TileClasses.rock);
     938        createObjectGroups(group, 0, constraint, count, 100);
     939    }
     940}
     941
     942/**
     943 * Create straggler trees.
     944 */
     945function addStragglerTrees(constraint, size, deviation, fill)
     946{
     947    deviation = deviation || g_DefaultDeviation;
     948    size = size || 1;
     949    fill = fill || 1;
     950
     951    // Ensure minimum distribution on african biome
     952    if (g_MapInfo.biome == g_BiomeSavanna)
     953    {
     954        fill = Math.max(fill, 2);
     955        size = Math.max(size, 1);
     956    }
     957
     958    var trees = [g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree4];
     959
     960    var treesPerPlayer = 40;
     961    var playerBonus = Math.max(1, (g_MapInfo.numPlayers - 3) / 2);
     962
     963    var offset = getRandomDeviation(size, deviation);
     964    var treeCount = treesPerPlayer * playerBonus * fill;
     965    var totalTrees = scaleByMapSize(treeCount, treeCount);
     966
     967    var count = Math.floor(totalTrees / trees.length) * fill;
     968    var min = 1 * offset;
     969    var max = 4 * offset;
     970    var minDist = 1 * offset;
     971    var maxDist = 5 * offset;
     972
     973    // More trees for the african biome
     974    if (g_MapInfo.biome == g_BiomeSavanna)
     975    {
     976        min = 3 * offset;
     977        max = 5 * offset;
     978        minDist = 2 * offset + 1;
     979        maxDist = 3 * offset + 2;
     980    }
     981
     982    for (var i = 0; i < trees.length; ++i)
     983    {
     984        var treesMax = max;
     985
     986        // Don't clump fruit trees
     987        if (i == 2 && (g_MapInfo.biome == g_BiomeDesert || g_MapInfo.biome == g_BiomeMediterranean))
     988            treesMax = 1;
     989
     990        min = Math.min(min, treesMax);
     991
     992        var group = new SimpleGroup([new SimpleObject(trees[i], min, treesMax, minDist, maxDist)], true, g_TileClasses.forest);
     993        createObjectGroups(group, 0, constraint, count);
     994    }
     995}
     996
     997///////////
     998// Terrain Helpers
     999///////////
     1000
     1001/**
     1002 * Determine if the endline of the bluff is within the tilemap.
     1003 *
     1004 * @returns {Number} 0 if the bluff is reachable, otherwise a positive number
     1005 */
     1006function unreachableBluff(bb, corners, baseLine, endLine)
     1007{
     1008    // If we couldn't find a slope line
     1009    if (typeof baseLine.midX === "undefined" || typeof endLine.midX === "undefined")
     1010        return 1;
     1011
     1012    // If the end points aren't on the tilemap
     1013    if (!g_Map.validT(endLine.x1, endLine.z1) && !g_Map.validT(endLine.x2, endLine.z2))
     1014        return 2;
     1015
     1016    var minTilesInGroup = 1;
     1017    var insideBluff = false;
     1018    var outsideBluff = false;
     1019
     1020    // If there aren't enough points in each row
     1021    for (var x = 0; x < bb.length; ++x)
     1022    {
     1023        var count = 0;
     1024        for (var z = 0; z < bb[x].length; ++z)
     1025        {
     1026            if (!bb[x][z].isFeature)
     1027                continue;
     1028
     1029            var valid = g_Map.validT(x + corners.minX, z + corners.minZ);
     1030
     1031            if (valid)
     1032                ++count;
     1033
     1034            if (!insideBluff && valid)
     1035                insideBluff = true;
     1036
     1037            if (outsideBluff && valid)
     1038                return 3;
     1039        }
     1040
     1041        // We're expecting the end of the bluff
     1042        if (insideBluff && count < minTilesInGroup)
     1043            outsideBluff = true;
     1044    }
     1045
     1046    var insideBluff = false;
     1047    var outsideBluff = false;
     1048
     1049    // If there aren't enough points in each column
     1050    for (var z = 0; z < bb[0].length; ++z)
     1051    {
     1052        var count = 0;
     1053        for (var x = 0; x < bb.length; ++x)
     1054        {
     1055            if (!bb[x][z].isFeature)
     1056                continue;
     1057
     1058            var valid = g_Map.validT(x + corners.minX, z + corners.minZ);
     1059
     1060            if (valid)
     1061                ++count;
     1062
     1063            if (!insideBluff && valid)
     1064                insideBluff = true;
     1065
     1066            if (outsideBluff && valid)
     1067                return 3;
     1068        }
     1069
     1070        // We're expecting the end of the bluff
     1071        if (insideBluff && count < minTilesInGroup)
     1072            outsideBluff = true;
     1073    }
     1074
     1075    // Bluff is reachable
     1076    return 0;
     1077}
     1078
     1079/**
     1080 * Remove the bluff class and turn it into a plateau.
     1081 */
     1082function removeBluff(points)
     1083{
     1084    for (var i = 0; i < points.length; ++i)
     1085        addToClass(points[i].x, points[i].z, g_TileClasses.mountain);
     1086}
     1087
     1088/**
     1089 * Create an array of points the fill a bounding box around a terrain feature.
     1090 */
     1091function createBoundingBox(points, corners)
     1092{
     1093    var bb = [];
     1094    var width = corners.maxX - corners.minX + 1;
     1095    var length = corners.maxZ - corners.minZ + 1;
     1096    for (var w = 0; w < width; ++w)
     1097    {
     1098        bb[w] = [];
     1099        for (var l = 0; l < length; ++l)
     1100        {
     1101            var curHeight = g_Map.getHeight(w + corners.minX, l + corners.minZ);
     1102            bb[w][l] = {
     1103                "height": curHeight,
     1104                "isFeature": false
     1105            };
     1106        }
     1107    }
     1108
     1109    // Define the coordinates that represent the bluff
     1110    for (var p = 0; p < points.length; ++p)
     1111    {
     1112        var pt = points[p];
     1113        bb[pt.x - corners.minX][pt.z - corners.minZ].isFeature = true;
     1114    }
     1115
     1116    return bb;
     1117}
     1118
     1119/**
     1120 * Flattens the ground touching a terrain feature.
     1121 */
     1122function fadeToGround(bb, minX, minZ, elevation)
     1123{
     1124    var ground = createTerrain(g_Terrains.mainTerrain);
     1125    for (var x = 0; x < bb.length; ++x)
     1126        for (var z = 0; z < bb[x].length; ++z)
     1127        {
     1128            var pt = bb[x][z];
     1129            if (!pt.isFeature && nextToFeature(bb, x, z))
     1130            {
     1131                var newEl = smoothElevation(x + minX, z + minZ);
     1132                g_Map.setHeight(x + minX, z + minZ, newEl);
     1133                ground.place(x + minX, z + minZ);
     1134            }
     1135        }
     1136}
     1137
     1138/**
     1139 * Find a 45 degree line in a bounding box that does not intersect any terrain feature.
     1140 */
     1141function findClearLine(bb, corners, angle)
     1142{
     1143    // Angle - 0: northwest; 1: northeast; 2: southeast; 3: southwest
     1144    var z = corners.maxZ;
     1145    var xOffset = -1;
     1146    var zOffset = -1;
     1147
     1148    switch(angle)
     1149    {
     1150        case 1:
     1151            xOffset = 1;
     1152            break;
     1153        case 2:
     1154            xOffset = 1;
     1155            zOffset = 1;
     1156            z = corners.minZ;
     1157            break;
     1158        case 3:
     1159            zOffset = 1;
     1160            z = corners.minZ;
     1161            break;
     1162    }
     1163
     1164    var clearLine = {};
     1165
     1166    for (var x = corners.minX; x <= corners.maxX; ++x)
     1167    {
     1168        var x2 = x;
     1169        var z2 = z;
     1170
     1171        var clear = true;
     1172
     1173        while (x2 >= corners.minX && x2 <= corners.maxX && z2 >= corners.minZ && z2 <= corners.maxZ)
     1174        {
     1175            var bp = bb[x2 - corners.minX][z2 - corners.minZ];
     1176            if (bp.isFeature && g_Map.validT(x2, z2))
     1177            {
     1178                clear = false;
     1179                break;
     1180            }
     1181
     1182            x2 = x2 + xOffset;
     1183            z2 = z2 + zOffset;
     1184        }
     1185
     1186        if (clear)
     1187        {
     1188            var lastX = x2 - xOffset;
     1189            var lastZ = z2 - zOffset;
     1190            var midX = Math.floor((x + lastX) / 2);
     1191            var midZ = Math.floor((z + lastZ) / 2);
     1192            clearLine = {
     1193                "x1": x,
     1194                "z1": z,
     1195                "x2": lastX,
     1196                "z2": lastZ,
     1197                "midX": midX,
     1198                "midZ": midZ,
     1199                "height": g_MapInfo.mapHeight
     1200            };
     1201        }
     1202
     1203        if (clear && (angle == 1 || angle == 2))
     1204            break;
     1205
     1206        if (!clear && (angle == 0 || angle == 3))
     1207            break;
     1208    }
     1209
     1210    return clearLine;
     1211}
     1212
     1213/**
     1214 * Returns the corners of a bounding box.
     1215 */
     1216function findCorners(points)
     1217{
     1218    // Find the bounding box of the terrain feature
     1219    var minX = g_MapInfo.mapSize + 1;
     1220    var minZ = g_MapInfo.mapSize + 1;
     1221    var maxX = -1;
     1222    var maxZ = -1;
     1223
     1224    for (var p = 0; p < points.length; ++p)
     1225    {
     1226        var pt = points[p];
     1227
     1228        minX = Math.min(pt.x, minX);
     1229        minZ = Math.min(pt.z, minZ);
     1230
     1231        maxX = Math.max(pt.x, maxX);
     1232        maxZ = Math.max(pt.z, maxZ);
     1233    }
     1234
     1235    return {
     1236        "minX": minX,
     1237        "minZ": minZ,
     1238        "maxX": maxX,
     1239        "maxZ": maxZ
     1240    };
     1241}
     1242
     1243/**
     1244 * Finds the average elevation around a point.
     1245 */
     1246function smoothElevation(x, z)
     1247{
     1248    var min = g_Map.getHeight(x, z);
     1249
     1250    for (var xOffset = -1; xOffset <= 1; ++xOffset)
     1251        for (var zOffset = -1; zOffset <= 1; ++zOffset)
     1252        {
     1253            var thisX = x + xOffset;
     1254            var thisZ = z + zOffset;
     1255            if (!g_Map.validT(thisX, thisZ))
     1256                continue;
     1257
     1258            var height = g_Map.getHeight(thisX, thisZ);
     1259            if (height < min)
     1260                min = height;
     1261        }
     1262
     1263    return min;
     1264}
     1265
     1266/**
     1267 * Determines if a point in a bounding box array is next to a terrain feature.
     1268 */
     1269function nextToFeature(bb, x, z)
     1270{
     1271    for (var xOffset = -1; xOffset <= 1; ++xOffset)
     1272        for (var zOffset = -1; zOffset <= 1; ++zOffset)
     1273        {
     1274            var thisX = x + xOffset;
     1275            var thisZ = z + zOffset;
     1276            if (thisX < 0 || thisX >= bb.length || thisZ < 0 || thisZ >= bb[x].length || (thisX == 0 && thisZ == 0))
     1277                continue;
     1278
     1279            if (bb[thisX][thisZ].isFeature)
     1280                return true;
     1281        }
     1282
     1283    return false;
     1284}
     1285
     1286/**
     1287 * Returns a number within a random deviation of a base number.
     1288 */
     1289function getRandomDeviation(base, deviation)
     1290{
     1291    deviation = Math.min(base, deviation);
     1292    deviation = base + randInt(20 * deviation) / 10 - deviation;
     1293    return deviation.toFixed(2);
     1294}
  • binaries/data/mods/public/maps/random/rm-team/setup.js

     
     1const g_Amounts = {
     2    "scarce": 0.2,
     3    "few": 0.5,
     4    "normal": 1,
     5    "many": 1.75,
     6    "tons": 3
     7};
     8
     9const g_Mixes = {
     10    "same": 0,
     11    "similar": 0.1,
     12    "normal": 0.25,
     13    "varied": 0.5,
     14    "unique": 0.75
     15};
     16
     17const g_Sizes = {
     18    "tiny": 0.5,
     19    "small": 0.75,
     20    "normal": 1,
     21    "big": 1.25,
     22    "huge": 1.5,
     23};
     24
     25const g_AllAmounts = Object.keys(g_Amounts);
     26const g_AllMixes = Object.keys(g_Mixes);
     27const g_AllSizes = Object.keys(g_Sizes);
     28
     29const g_DefaultTileClasses = [
     30    "animals",
     31    "baseResource",
     32    "berries",
     33    "bluff",
     34    "bluffSlope",
     35    "dirt",
     36    "fish",
     37    "food",
     38    "forest",
     39    "hill",
     40    "land",
     41    "map",
     42    "metal",
     43    "mountain",
     44    "plateau",
     45    "player",
     46    "prop",
     47    "ramp",
     48    "rock",
     49    "settlement",
     50    "spine",
     51    "valley",
     52    "water"
     53];
     54
     55var g_MapInfo;
     56var g_TileClasses;
     57var g_Forests;
     58
     59/**
     60 * Adds an array of elements to the map.
     61 */
     62function addElements(els)
     63{
     64    for (var i = 0; i < els.length; ++i)
     65    {
     66        var stay = null;
     67        if (els[i].stay !== undefined)
     68            stay = els[i].stay;
     69
     70        els[i].func(
     71            [avoidClasses.apply(null, els[i].avoid), stayClasses.apply(null, stay)],
     72            pickSize(els[i].sizes),
     73            pickMix(els[i].mixes),
     74            pickAmount(els[i].amounts)
     75        );
     76    }
     77}
     78
     79/**
     80 * Converts "amount" terms to numbers.
     81 */
     82function pickAmount(amounts)
     83{
     84    var amount = amounts[randInt(amounts.length)];
     85
     86    if (amount in g_Amounts)
     87        return g_Amounts[amount];
     88
     89    return g_Mixes.normal;
     90}
     91
     92/**
     93 * Converts "mix" terms to numbers.
     94 */
     95function pickMix(mixes)
     96{
     97    var mix = mixes[randInt(mixes.length)];
     98
     99    if (mix in g_Mixes)
     100        return g_Mixes[mix];
     101
     102    return g_Mixes.normal;
     103}
     104
     105/**
     106 * Converts "size" terms to numbers.
     107 */
     108function pickSize(sizes)
     109{
     110    var size = sizes[randInt(sizes.length)];
     111
     112    if (size in g_Sizes)
     113        return g_Sizes[size];
     114
     115    return g_Sizes.normal;
     116}
     117
     118/**
     119 * Paints the entire map with a single tile type.
     120 */
     121function resetTerrain(terrain, tc, elevation)
     122{
     123    g_MapInfo.mapSize = getMapSize();
     124    g_MapInfo.mapArea = g_MapInfo.mapSize * g_MapInfo.mapSize;
     125    g_MapInfo.centerOfMap = Math.floor(g_MapInfo.mapSize / 2);
     126    g_MapInfo.mapRadius = -PI / 4;
     127
     128    var placer = new ClumpPlacer(g_MapInfo.mapArea, 1, 1, 1, g_MapInfo.centerOfMap, g_MapInfo.centerOfMap);
     129    var terrainPainter = new LayeredPainter([terrain], []);
     130    var elevationPainter = new SmoothElevationPainter(ELEVATION_SET, elevation, 1);
     131    createArea(placer, [terrainPainter, elevationPainter, paintClass(tc)], null);
     132
     133    g_MapInfo.mapHeight = elevation;
     134}
     135
     136/**
     137 * Euclidian distance between two points.
     138 */
     139function euclid_distance(x1, z1, x2, z2)
     140{
     141    return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(z2 - z1, 2));
     142}
     143
     144/**
     145 * Chose starting locations for the given players.
     146 *
     147 * @param {string} type - "radial", "stacked", "stronghold", "random"
     148 * @param {number} distance - radial distance from the center of the map
     149 */
     150function addBases(type, distance, groupedDistance)
     151{
     152    type = type || "radial";
     153    distance = distance || 0.3;
     154    groupedDistance = groupedDistance || 0.05;
     155
     156    var playerIDs = randomizePlayers();
     157    var players = {};
     158
     159    switch(type)
     160    {
     161        case "line":
     162            players = placeLine(playerIDs, distance, groupedDistance);
     163            break;
     164        case "radial":
     165            players = placeRadial(playerIDs, distance);
     166            break;
     167        case "random":
     168            players = placeRandom(playerIDs);
     169            break;
     170        case "stronghold":
     171            players = placeStronghold(playerIDs, distance, groupedDistance);
     172            break;
     173    }
     174
     175    return players;
     176}
     177
     178/**
     179 * Create the base for a single player.
     180 *
     181 * @param {Object} - contains id, angle, x, z
     182 * @param {boolean} - Whether or not iberian gets starting walls
     183 */
     184function createBase(player, walls)
     185{
     186    // Get the x and z in tiles
     187    var fx = fractionToTiles(player.x);
     188    var fz = fractionToTiles(player.z);
     189    var ix = round(fx);
     190    var iz = round(fz);
     191    addToClass(ix, iz, g_TileClasses.player);
     192    addToClass(ix + 5, iz, g_TileClasses.player);
     193    addToClass(ix, iz + 5, g_TileClasses.player);
     194    addToClass(ix - 5, iz, g_TileClasses.player);
     195    addToClass(ix, iz - 5, g_TileClasses.player);
     196
     197    // Create starting units
     198    if ((walls || walls === undefined) && g_MapInfo.mapSize > 192)
     199        placeCivDefaultEntities(fx, fz, player.id, g_MapInfo.mapRadius);
     200    else
     201        placeCivDefaultEntities(fx, fz, player.id, g_MapInfo.mapRadius, { 'iberWall': false });
     202
     203    // Create the city patch
     204    var cityRadius = scaleByMapSize(15, 25) / 3;
     205    var placer = new ClumpPlacer(PI * cityRadius * cityRadius, 0.6, 0.3, 10, ix, iz);
     206    var painter = new LayeredPainter([g_Terrains.roadWild, g_Terrains.road], [1]);
     207    createArea(placer, painter, null);
     208
     209    // Create initial berry bushes at random angle
     210    var bbAngle = randFloat(0, TWO_PI);
     211    var bbDist = 10;
     212    var bbX = round(fx + bbDist * cos(bbAngle));
     213    var bbZ = round(fz + bbDist * sin(bbAngle));
     214    group = new SimpleGroup(
     215        [new SimpleObject(g_Gaia.fruitBush, 5, 5, 0, 3)],
     216        true, g_TileClasses.baseResource, bbX, bbZ
     217    );
     218    createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 2));
     219
     220    // Create metal mine at a different angle
     221    var mAngle = bbAngle;
     222    while(abs(mAngle - bbAngle) < PI / 3)
     223        mAngle = randFloat(0, TWO_PI);
     224
     225    var mDist = 12;
     226    var mX = round(fx + mDist * cos(mAngle));
     227    var mZ = round(fz + mDist * sin(mAngle));
     228    group = new SimpleGroup(
     229        [new SimpleObject(g_Gaia.metalLarge, 1, 1, 0, 0)],
     230        true, g_TileClasses.baseResource, mX, mZ
     231    );
     232    createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 2));
     233
     234    // Create stone mine beside metal
     235    mAngle += randFloat(PI / 8, PI / 4);
     236    mX = round(fx + mDist * cos(mAngle));
     237    mZ = round(fz + mDist * sin(mAngle));
     238    group = new SimpleGroup(
     239        [new SimpleObject(g_Gaia.stoneLarge, 1, 1, 0, 2)],
     240        true, g_TileClasses.baseResource, mX, mZ
     241    );
     242    createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 2));
     243
     244    // Create initial chicken
     245    for (var j = 0; j < 2; ++j)
     246    {
     247        for (var tries = 0; tries < 10; ++tries)
     248        {
     249            var aAngle = randFloat(0, TWO_PI);
     250            var aDist = 9;
     251            var aX = round(fx + aDist * cos(aAngle));
     252            var aZ = round(fz + aDist * sin(aAngle));
     253
     254            var group = new SimpleGroup(
     255                [new SimpleObject(g_Gaia.chicken, 5, 5, 0, 2)],
     256                true, g_TileClasses.baseResource, aX, aZ
     257            );
     258
     259            if (createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 4)))
     260                break;
     261        }
     262    }
     263
     264    var hillSize = PI * g_MapInfo.mapRadius * g_MapInfo.mapRadius;
     265
     266    // Create starting trees
     267    var num = g_MapInfo.biome == g_BiomeSavanna ? 5 : 15;
     268    for (var tries = 0; tries < 10; ++tries)
     269    {
     270        var tAngle = randFloat(0, TWO_PI);
     271        var tDist = randFloat(12, 13);
     272        var tX = round(fx + tDist * cos(tAngle));
     273        var tZ = round(fz + tDist * sin(tAngle));
     274
     275        group = new SimpleGroup(
     276            [new SimpleObject(g_Gaia.tree1, num, num, 1, 3)],
     277            false, g_TileClasses.baseResource, tX, tZ
     278        );
     279
     280        if (createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 4)))
     281            break;
     282    }
     283
     284    // Create grass tufts
     285    var num = hillSize / 250;
     286    for (var j = 0; j < num; ++j)
     287    {
     288        var gAngle = randFloat(0, TWO_PI);
     289        var gDist = g_MapInfo.mapRadius - (5 + randInt(7));
     290        var gX = round(fx + gDist * cos(gAngle));
     291        var gZ = round(fz + gDist * sin(gAngle));
     292        group = new SimpleGroup(
     293            [new SimpleObject(g_Decoratives.grassShort, 2, 5, 0, 1, -PI / 8, PI / 8)],
     294            false, g_TileClasses.baseResource, gX, gZ
     295        );
     296        createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 4));
     297    }
     298}
     299
     300/**
     301 * Return an array where each element is an array of playerIndices of a team.
     302 */
     303function getTeams(numPlayers)
     304{
     305    // Group players by team
     306    var teams = [];
     307    for (var i = 0; i < numPlayers; ++i)
     308    {
     309        let team = getPlayerTeam(i);
     310        if (team == -1)
     311            continue;
     312
     313        if (!teams[team])
     314            teams[team] = [];
     315
     316        teams[team].push(i+1);
     317    }
     318
     319    // Players without a team get a custom index
     320    for (var i = 0; i < numPlayers; ++i)
     321        if (getPlayerTeam(i) == -1)
     322            teams.push([i+1]);
     323
     324    // Remove unused indices
     325    return teams.filter(team => true);
     326}
     327
     328/**
     329 * Chose a random pattern for placing the bases of the players.
     330 */
     331function randomStartingPositionPattern()
     332{
     333    var formats = ["radial"];
     334
     335    // Enable stronghold if we have a few teams and a big enough map
     336    if (g_MapInfo.teams.length >= 2 && g_MapInfo.numPlayers >= 4 && g_MapInfo.mapSize >= 256)
     337        formats.push("stronghold");
     338
     339    // Enable random if we have enough teams or enough players on a big enough map
     340    if (g_MapInfo.mapSize >= 256 && (g_MapInfo.teams.length >= 3 || g_MapInfo.numPlayers > 4))
     341        formats.push("random");
     342
     343    // Enable line if we have enough teams and players on a big enough map
     344    if (g_MapInfo.teams.length >= 2 && g_MapInfo.numPlayers >= 4 && g_MapInfo.mapSize >= 384)
     345        formats.push("line");
     346
     347    return {
     348        "setup": formats[randInt(formats.length)],
     349        "distance": randFloat(0.2, 0.35),
     350        "separation": randFloat(0.05, 0.1)
     351    };
     352}
     353
     354/**
     355 * Mix player indices but sort by team.
     356 *
     357 * @returns {Array} - every item is an array of player indices
     358 */
     359function randomizePlayers()
     360{
     361    var playerIDs = [];
     362    for (var i = 0; i < g_MapInfo.numPlayers; ++i)
     363        playerIDs.push(i + 1);
     364
     365    return sortPlayers(playerIDs);
     366}
     367
     368/**
     369 * Place teams in a line-pattern.
     370 *
     371 * @param {Array} playerIDs - typically randomized indices of players of a single team
     372 * @param {number} distance - radial distance from the center of the map
     373 * @param {number} groupedDistance - distance between players
     374 *
     375 * @returns {Array} - contains id, angle, x, z for every player
     376 */
     377function placeLine(playerIDs, distance, groupedDistance)
     378{
     379    var players = [];
     380
     381    for (var i = 0; i < g_MapInfo.teams.length; ++i)
     382    {
     383        var safeDist = distance;
     384        if (distance + g_MapInfo.teams[i].length * groupedDistance > 0.45)
     385            safeDist = 0.45 - g_MapInfo.teams[i].length * groupedDistance;
     386
     387        var teamAngle = g_MapInfo.startAngle + (i + 1) * TWO_PI / g_MapInfo.teams.length;
     388
     389        // Create player base
     390        for (var p = 0; p < g_MapInfo.teams[i].length; ++p)
     391        {
     392            players[g_MapInfo.teams[i][p]] = {
     393                "id": g_MapInfo.teams[i][p],
     394                "angle": g_MapInfo.startAngle + (p + 1) * TWO_PI / g_MapInfo.teams[i].length,
     395                "x": 0.5 + (safeDist + p * groupedDistance) * cos(teamAngle),
     396                "z": 0.5 + (safeDist + p * groupedDistance) * sin(teamAngle)
     397            };
     398            createBase(players[g_MapInfo.teams[i][p]], false);
     399        }
     400    }
     401
     402    return players;
     403}
     404
     405/**
     406 * Place players in a circle-pattern.
     407 *
     408 * @param {number} distance - radial distance from the center of the map
     409 */
     410function placeRadial(playerIDs, distance)
     411{
     412    var players = new Array(g_MapInfo.numPlayers);
     413
     414    for (var i = 0; i < g_MapInfo.numPlayers; ++i)
     415    {
     416        var angle = g_MapInfo.startAngle + i * TWO_PI / g_MapInfo.numPlayers;
     417        players[i] = {
     418            "id": playerIDs[i],
     419            "angle": angle,
     420            "x": 0.5 + distance * cos(angle),
     421            "z": 0.5 + distance * sin(angle)
     422        };
     423        createBase(players[i]);
     424    }
     425
     426    return players;
     427}
     428
     429/**
     430 * Chose arbitrary starting locations.
     431 */
     432function placeRandom(playerIDs)
     433{
     434    var players = [];
     435    var placed = [];
     436
     437    for (var i = 0; i < g_MapInfo.numPlayers; ++i)
     438    {
     439        var attempts = 0;
     440        var playerAngle = randFloat(0, TWO_PI);
     441        var distance = randFloat(0, 0.42);
     442        var x = 0.5 + distance * cos(playerAngle);
     443        var z = 0.5 + distance * sin(playerAngle);
     444
     445        var tooClose = false;
     446        for (var j = 0; j < placed.length; ++j)
     447        {
     448            var sep = euclid_distance(x, z, placed[j].x, placed[j].z);
     449            if (sep < 0.25)
     450            {
     451                tooClose = true;
     452                break;
     453            }
     454        }
     455
     456        if (tooClose)
     457        {
     458            --i;
     459            ++attempts;
     460
     461            // Reset if we're in what looks like an infinite loop
     462            if (attempts > 100)
     463            {
     464                players = [];
     465                placed = [];
     466                i = -1;
     467                attempts = 0;
     468            }
     469
     470            continue;
     471        }
     472
     473        players[i] = {
     474            "id": playerIDs[i],
     475            "angle": playerAngle,
     476            "x": x,
     477            "z": z
     478        };
     479
     480        placed.push(players[i]);
     481    }
     482
     483    // Create the bases
     484    for (var i = 0; i < g_MapInfo.numPlayers; ++i)
     485        createBase(players[i]);
     486
     487    return players;
     488}
     489
     490/**
     491 * Place given players in a stronghold-pattern.
     492 *
     493 * @param distance - radial distance from the center of the map
     494 * @param groupedDistance - distance between neighboring players
     495 */
     496function placeStronghold(playerIDs, distance, groupedDistance)
     497{
     498    var players = [];
     499
     500    for (var i = 0; i < g_MapInfo.teams.length; ++i)
     501    {
     502        var teamAngle = g_MapInfo.startAngle + (i + 1) * TWO_PI / g_MapInfo.teams.length;
     503        var fractionX = 0.5 + distance * cos(teamAngle);
     504        var fractionZ = 0.5 + distance * sin(teamAngle);
     505
     506        // If we have a team of above average size, make sure they're spread out
     507        if (g_MapInfo.teams[i].length > 4)
     508            groupedDistance = randFloat(0.08, 0.12);
     509
     510        // If we have a team of below average size, make sure they're together
     511        if (g_MapInfo.teams[i].length < 3)
     512            groupedDistance = randFloat(0.04, 0.06);
     513
     514        // If we have a solo player, place them on the center of the team's location
     515        if (g_MapInfo.teams[i].length == 1)
     516            groupedDistance = 0;
     517
     518        // Create player base
     519        for (var p = 0; p < g_MapInfo.teams[i].length; ++p)
     520        {
     521            var angle = g_MapInfo.startAngle + (p + 1) * TWO_PI / g_MapInfo.teams[i].length;
     522            players[g_MapInfo.teams[i][p]] = {
     523                "id": g_MapInfo.teams[i][p],
     524                "angle": angle,
     525                "x": fractionX + groupedDistance * cos(angle),
     526                "z": fractionZ + groupedDistance * sin(angle)
     527            };
     528            createBase(players[g_MapInfo.teams[i][p]], false);
     529        }
     530    }
     531
     532    return players;
     533}
     534
     535/**
     536 * Creates tileClass for the default classes and every class given.
     537 *
     538 * @param {Array} newClasses
     539 * @returns {Object} - maps from classname to ID
     540 */
     541function initTileClasses(newClasses)
     542{
     543    var classNames = g_DefaultTileClasses;
     544
     545    if (newClasses !== undefined)
     546        classNames = classNames.concat(newClasses);
     547
     548    g_TileClasses = {};
     549    for (var className of classNames)
     550        g_TileClasses[className] = createTileClass();
     551}
     552
     553/**
     554 * Get biome-specific names of entities and terrain after randomization.
     555 */
     556function initBiome()
     557{
     558    g_Forests = {
     559        "forest1": [
     560            g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree1,
     561            g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree2,
     562            g_Terrains.forestFloor2
     563        ],
     564        "forest2": [
     565            g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree4,
     566            g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree5,
     567            g_Terrains.forestFloor1
     568        ]
     569    };
     570}
     571
     572/**
     573 * Creates an object of commonly used functions.
     574 */
     575function initMapSettings()
     576{
     577    initBiome();
     578
     579    let numPlayers = getNumPlayers();
     580    g_MapInfo = {
     581        "biome": g_BiomeID,
     582        "numPlayers": numPlayers,
     583        "teams": getTeams(numPlayers),
     584        "startAngle": randFloat(0, TWO_PI)
     585    };
     586}
  • binaries/data/mods/public/maps/random/stronghold.js

     
    11RMS.LoadLibrary("rmgen");
     2RMS.LoadLibrary("rm-team");
     3
    24InitMap();
    35
    46randomizeBiome();