Ticket #4245: add_caledonian_meadows2016_9_25c.diff

File add_caledonian_meadows2016_9_25c.diff, 31.6 KB (added by FeXoR, 8 years ago)

More resource spots also including raider camps. Decreased incline on smaller maps.

  • binaries/data/mods/public/art/textures/ui/session/icons/mappreview/caledonian_meadows.png

    Cannot display: file marked as a binary type.
    svn:mime-type = application/octet-stream
  • binaries/data/mods/public/maps/random/caledonian_meadows.js

    Property changes on: binaries/data/mods/public/art/textures/ui/session/icons/mappreview/caledonian_meadows.png
    ___________________________________________________________________
    Added: svn:mime-type
    ## -0,0 +1 ##
    +application/octet-stream
    \ No newline at end of property
     
     1/**
     2 * ToDo:
     3 * Place start locations of one team close to each other
     4 */
     5 
     6RMS.LoadLibrary("rmgen");
     7RMS.LoadLibrary("heightmap");
     8
     9InitMap();
     10
     11/**
     12 * Start Timer
     13 */
     14let genStartTime = new Date().getTime();
     15
     16/**
     17 * Returns an approximation of the heights of the tiles between the vertices, a tile centered heightmap
     18 * A tile centered heightmap is one smaller in width and height than an ordinary heightmap
     19 * It is meant to e.g. texture a map by height (x/y coordinates correspond to those of the terrain texture map)
     20 * Don't use this to override g_Map height (Potentially breaks the map)!
     21 * @param {array} [heightmap=g_Map.height] - A reliefmap the tile centered version should be build from
     22 */
     23function getTileCenteredHeightmap(heightmap = g_Map.height)
     24{
     25    let max_x = heightmap.length - 1;
     26    let max_y = heightmap[0].length - 1;
     27    let tchm = [];
     28    for (let x = 0; x < max_x; ++x)
     29    {
     30        tchm[x] = new Float32Array(max_y);
     31        for (let y = 0; y < max_y; ++y)
     32        {
     33            tchm[x][y] = 0.25 * (heightmap[x][y] + heightmap[x + 1][y] + heightmap[x][y + 1] + heightmap[x + 1][y + 1]);
     34        }
     35    }
     36    return tchm;
     37}
     38
     39/**
     40 * Returns an inclination map corresponding to the tiles between the heightmaps vertices:
     41 * array of heightmap width-1 arrays of height-1 vectors (associative arrays) of the from:
     42 * {"x": x_slope, "y": y_slope] so a 2D Vector pointing to the hightest incline (with the length the incline in the vectors direction)
     43 * The x and y coordinates of a tile in the terrain texture map correspond to those of the inclination map
     44 * @param {array} [heightmap=g_Map.height] - The reliefmap the inclination map is to be generated from
     45 */
     46function getInclineMap(heightmap)
     47{
     48    heightmap = (heightmap || g_Map.height);
     49    let max_x = heightmap.length - 1;
     50    let max_y = heightmap[0].length - 1;
     51    let inclineMap = [];
     52    for (let x = 0; x < max_x; ++x)
     53    {
     54        inclineMap[x] = [];
     55        for (let y = 0; y < max_y; ++y)
     56        {
     57            let dx = heightmap[x + 1][y] - heightmap[x][y];
     58            let dy = heightmap[x][y + 1] - heightmap[x][y];
     59            let next_dx = heightmap[x + 1][y + 1] - heightmap[x][y + 1];
     60            let next_dy = heightmap[x + 1][y + 1] - heightmap[x + 1][y];
     61            inclineMap[x][y] = {"x": 0.5 * (dx + next_dx), "y": 0.5 * (dy + next_dy)};
     62        }
     63    }
     64    return inclineMap;
     65}
     66
     67/**
     68 * Returns a slope map (same form as the a heightmap with one less width and height)
     69 * Not normalized. Only returns the steepness (float), not the direction of incline.
     70 * The x and y coordinates of a tile in the terrain texture map correspond to those of the slope map
     71 * @param {array} [inclineMap=getInclineMap(g_Map.height)] - A map with the absolute inclination for each tile
     72 */
     73function getSlopeMap(inclineMap = getInclineMap(g_Map.height))
     74{
     75    let max_x = inclineMap.length;
     76    let slopeMap = [];
     77    for (let x = 0; x < max_x; ++x)
     78    {
     79        let max_y = inclineMap[x].length;
     80        slopeMap[x] = new Float32Array(max_y);
     81        for (let y = 0; y < max_y; ++y)
     82            slopeMap[x][y] = Math.pow(inclineMap[x][y].x * inclineMap[x][y].x + inclineMap[x][y].y * inclineMap[x][y].y, 0.5);
     83    }
     84    return slopeMap;
     85}
     86
     87/**
     88 * Returns the order to go through the points for the shortest closed path (array of indices)
     89 * @param {array} [points] - Points to be sorted of the form {"x": x_value, "y": y_value}
     90 */
     91function getOrderOfPointsForShortestClosePath(points)
     92{
     93    let order = [];
     94    let distances = [];
     95    if (points.length <= 3)
     96    {
     97        for (let i = 0; i < points.length; ++i)
     98            order.push(i);
     99       
     100        return order;
     101    }
     102   
     103    // Just add the first 3 points
     104    let pointsToAdd = deepcopy(points);
     105    for (let i = 0; i < 3; ++i)
     106    {
     107        order.push(i)
     108        pointsToAdd.shift(i);
     109        if (i)
     110            distances.push(getDistance(points[order[i]].x, points[order[i]].y, points[order[i - 1]].x, points[order[i - 1]].y));
     111    }
     112    distances.push(getDistance(points[order[0]].x, points[order[0]].y, points[order[order.length - 1]].x, points[order[order.length - 1]].y))
     113   
     114    // Add remaining points so the path lengthens the least
     115    let numPointsToAdd = pointsToAdd.length;
     116    for (let i = 0; i < numPointsToAdd; ++i)
     117    {
     118        let indexToAddTo = undefined;
     119        let minEnlengthen = Infinity;
     120        let minDist1 = 0;
     121        let minDist2 = 0;
     122        for (let k = 0; k < order.length; ++k)
     123        {
     124            let dist1 = getDistance(pointsToAdd[0].x, pointsToAdd[0].y, points[order[k]].x, points[order[k]].y);
     125            let dist2 = getDistance(pointsToAdd[0].x, pointsToAdd[0].y, points[order[(k + 1) % order.length]].x, points[order[(k + 1) % order.length]].y);
     126            let enlengthen = dist1 + dist2 - distances[k];
     127            if (enlengthen < minEnlengthen)
     128            {
     129                indexToAddTo = k;
     130                minEnlengthen = enlengthen;
     131                minDist1 = dist1;
     132                minDist2 = dist2;
     133            }
     134        }
     135        order.splice(indexToAddTo + 1, 0, i + 3);
     136        distances.splice(indexToAddTo, 1, minDist1, minDist2);
     137        pointsToAdd.shift();
     138    }
     139   
     140    return order;
     141}
     142
     143/**
     144 * Drags a path to a target height smoothing it at the edges and return some points along the path.
     145 *
     146 * TODO:
     147 * Would be nice to tell the function what to do and how often in the arguments
     148 * Adding painted tiles to a tile class
     149 */
     150function placeRandomPathToHeight(start, pathTexture, target, targetHeight, width = 10, occurrence = 2, strength = 0.1, heightmap = g_Map.height)
     151{
     152    if (pathTexture === true)
     153        pathTexture = ['temp_road', "temp_road_overgrown", 'temp_grass_b'];
     154   
     155    let clTempPath = createTileClass();
     156    let targetReached = false;
     157    let position = deepcopy(start);
     158    while (!targetReached)
     159    {
     160        rectangularSmoothToHeight(position, width, width, targetHeight, strength, heightmap);
     161        if (pathTexture)
     162            createArea(new ClumpPlacer(0.2 * width * width, 1, 1, 1, floor(position.x), floor(position.y)), [new TerrainPainter(pathTexture), paintClass(clTempPath)]);
     163       
     164        // Set lets for next loop
     165        let angleToTarget = getAngle(position.x, position.y, target.x, target.y);
     166        let angleOff = PI * (randFloat() - 0.5);
     167        position.x += occurrence * cos(angleToTarget + angleOff);
     168        position.y += occurrence * sin(angleToTarget + angleOff);
     169        if (getDistance(position.x, position.y, target.x, target.y) < occurrence / 2)
     170            targetReached = true;
     171    }
     172    return clTempPath;
     173}
     174
     175function getGrad(wrapped = true, scalarField = g_Map.height)
     176{
     177    let vectorField = [];
     178    let max_x = scalarField.length;
     179    let max_y = scalarField[0].length;
     180    if (!wrapped)
     181    {
     182        max_x -= 1;
     183        max_y -= 1;
     184    }
     185    for (let x = 0; x < max_x; ++x)
     186    {
     187        vectorField.push([]);
     188        for (let y = 0; y < max_y; ++y)
     189            vectorField[x].push({"x": scalarField[(x + 1) % max_x][y] - scalarField[x][y], "y": scalarField[x][(y + 1) % max_y] - scalarField[x][y]});
     190    }
     191       
     192    return vectorField;
     193}
     194
     195function splashErodeMap(strength = 1, heightmap = g_Map.height)
     196{
     197    let max_x = heightmap.length;
     198    let max_y = heightmap[0].length;
     199   
     200    let dHeight = getGrad(heightmap);
     201   
     202    for (let x = 0; x < max_x; ++x)
     203    {
     204        let next_x = (x + 1) % max_x;
     205        let prev_x = (x + max_x - 1) % max_x;
     206        for (let y = 0; y < max_y; ++y)
     207        {
     208            let next_y = (y + 1) % max_y;
     209            let prev_y = (y + max_y - 1) % max_y;
     210           
     211            let slopes = [- dHeight[x][y].x, - dHeight[x][y].y, dHeight[prev_x][y].x, dHeight[x][prev_y].y];
     212           
     213            let sumSlopes = 0;
     214            for (let i = 0; i < slopes.length; ++i)
     215                if (slopes[i] > 0)
     216                    sumSlopes += slopes[i];
     217           
     218            let drain = [];
     219            for (let i = 0; i < slopes.length; ++i)
     220            {
     221                drain.push(0);
     222                if (slopes[i] > 0)
     223                    drain[i] += min(strength * slopes[i] / sumSlopes, slopes[i]);
     224            }
     225           
     226            let sumDrain = 0;
     227            for (let i = 0; i < drain.length; ++i)
     228                sumDrain += drain[i];
     229           
     230            // Apply changes to maps
     231            heightmap[x][y] -= sumDrain;
     232            heightmap[next_x][y] += drain[0];
     233            heightmap[x][next_y] += drain[1];
     234            heightmap[prev_x][y] += drain[2];
     235            heightmap[x][prev_y] += drain[3];
     236        }
     237    }
     238}
     239
     240/**
     241 * Meant to place e.g. resource spots within a height range
     242 * @param {array} [heightRange] - The height range in which to place the entities (An associative array with keys "min" and "max" each containing a float)
     243 * @param {array} [avoidPoints] - An array of objects of the form {"x": int, "y": int, "dist": int}, points that will be avoided in the given dist e.g. start locations
     244 * @param {array} [avoidArea] - List of tiles to avoid
     245 * @param {integer} [minDistance=30] - How many tile widths the entities to place have to be away from each other, start locations and the map border
     246 * @param {array} [heightmap=g_Map.height] - The reliefmap the entities should be distributed on
     247 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared
     248 * @param {boolean} [isCircular=g_MapSettings.CircularMap] - If the map is circular or rectangular
     249 */
     250function getPointsByHeight(heightRange, avoidPoints, avoidArea, minDistance = 20, maxTries = 1000, heightmap = g_Map.height, isCircular = g_MapSettings.CircularMap)
     251{
     252    let points = [];
     253    let placements = deepcopy(avoidPoints);
     254    let validVertices = [];
     255    let r = 0.5 * (heightmap.length - 1); // Map center x/y as well as radius
     256    for (let x = minDistance; x < heightmap.length - minDistance; ++x)
     257    {
     258        for (let y = minDistance; y < heightmap[0].length - minDistance; ++y)
     259        {
     260            let isValid = true;
     261            for (let i = 0; i < pathArea.length; ++i)
     262                if (pathArea[i].x == x && pathArea[i].y == y)
     263                    isValid = false;
     264            if (!isValid)
     265                continue;
     266           
     267            if (heightmap[x][y] > heightRange.min && heightmap[x][y] < heightRange.max && (!isCircular || r - getDistance(x, y, r, r) >= minDistance)) // Has correct height and enough distance to map border
     268                validVertices.push({ "x": x, "y": y , "dist": minDistance});
     269        }
     270    }
     271   
     272    for (let tries = 0; tries < maxTries; ++tries)
     273    {
     274        let point = validVertices[randInt(validVertices.length)];
     275        if (placements.every(p => getDistance(p.x, p.y, point.x, point.y) > max(minDistance, p.dist)))
     276        {
     277            points.push(point);
     278            placements.push(point);
     279        }
     280    }
     281   
     282    return points;
     283}
     284
     285
     286/**
     287 * Design resource spots
     288 */
     289// Mines
     290let decorations = [
     291    "actor|geology/gray1.xml", "actor|geology/gray_rock1.xml",
     292    "actor|geology/highland1.xml", "actor|geology/highland2.xml", "actor|geology/highland3.xml",
     293    "actor|geology/highland_c.xml", "actor|geology/highland_d.xml", "actor|geology/highland_e.xml",
     294    "actor|props/flora/bush.xml", "actor|props/flora/bush_dry_a.xml", "actor|props/flora/bush_highlands.xml",
     295    "actor|props/flora/bush_tempe_a.xml", "actor|props/flora/bush_tempe_b.xml", "actor|props/flora/ferns.xml"
     296];
     297
     298function placeMine(point, centerEntity)
     299{
     300    placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI));
     301    let quantity = randInt(11, 23);
     302    let dAngle = TWO_PI / quantity;
     303    for (let i = 0; i < quantity; ++i)
     304    {
     305        let angle = i * dAngle + randFloat(0, dAngle);
     306        let dist = randFloat(2, 5);
     307        placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), decorations[randInt(0, decorations.length - 1)], 0, randFloat(0, TWO_PI));
     308    }
     309}
     310
     311// Food, fences with domestic animals
     312wallStyles["other"]["sheepIn"] = new WallElement("sheepIn", "gaia/fauna_sheep", PI / 4, -1.5, 0.75, PI/2);
     313wallStyles["other"]["foodBin"] = new WallElement("foodBin", "gaia/special_treasure_food_bin", PI/2, 1.5);
     314wallStyles["other"]["sheep"] = new WallElement("sheep", "gaia/fauna_sheep", 0, 0, 0.75);
     315wallStyles["other"]["farm"] = new WallElement("farm", "structures/brit_farmstead", PI, 0, -3);
     316let fences = [
     317    new Fortress("fence", ["foodBin", "farm", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence"]),
     318    new Fortress("fence", ["foodBin", "farm", "fence", "sheepIn", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence"]),
     319    new Fortress("fence", [
     320        "foodBin", "farm", "cornerIn", "bench", "cornerOut", "fence_short", "sheepIn", "fence", "sheepIn",
     321        "fence", "sheepIn", "fence_short", "sheep", "fence"
     322    ]),
     323    new Fortress("fence", [
     324        "foodBin", "farm", "cornerIn", "fence_short", "cornerOut", "bench", "sheepIn", "fence", "sheepIn",
     325        "fence", "sheepIn", "fence_short", "sheep", "fence"
     326    ]),
     327    new Fortress("fence", [
     328        "foodBin", "farm", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn",
     329        "fence_short", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence"
     330    ])
     331];
     332let num = fences.length;
     333for (let i = 0; i < num; ++i)
     334    fences.push(new Fortress("fence", deepcopy(fences[i].wall).reverse()));
     335
     336// Groves, only Wood
     337let groveEntities = [
     338    "gaia/flora_bush_temperate", "gaia/flora_tree_euro_beech"
     339];
     340let groveActors = [
     341    "actor|geology/highland1_moss.xml", "actor|geology/highland2_moss.xml",
     342    "actor|props/flora/bush.xml", "actor|props/flora/bush_dry_a.xml", "actor|props/flora/bush_highlands.xml",
     343    "actor|props/flora/bush_tempe_a.xml", "actor|props/flora/bush_tempe_b.xml", "actor|props/flora/ferns.xml"
     344];
     345let clGrove = createTileClass();
     346
     347function placeGrove(point)
     348{
     349    placeObject(point.x, point.y, ["structures/gaul_outpost", "gaia/flora_tree_oak_new"][randInt(0, 1)], 0, randFloat(0, TWO_PI));
     350    let quantity = randInt(20, 30);
     351    let dAngle = TWO_PI / quantity;
     352    for (let i = 0; i < quantity; ++i)
     353    {
     354        let angle = i * dAngle + randFloat(0, dAngle);
     355        let dist = randFloat(2, 5);
     356        let objectList = groveEntities;
     357        if (i % 3 == 0)
     358            objectList = groveActors;
     359        let x = point.x + dist * Math.cos(angle);
     360        let y = point.y + dist * Math.sin(angle);
     361        placeObject(x, y, objectList[randInt(0, objectList.length - 1)], 0, randFloat(0, TWO_PI));
     362        createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter("temp_grass_plants"), paintClass(clGrove)]);
     363    }
     364}
     365
     366// Camps with fire and gold treasure
     367function placeCamp(point,
     368    centerEntity = "actor|props/special/eyecandy/campfire.xml",
     369    otherEntities = ["gaia/special_treasure_metal", "gaia/special_treasure_standing_stone",
     370        "units/brit_infantry_slinger_b", "units/brit_infantry_javelinist_b", "units/gaul_infantry_slinger_b", "units/gaul_infantry_javelinist_b", "units/gaul_champion_fanatic",
     371        "actor|props/special/common/waypoint_flag.xml", "actor|props/special/eyecandy/barrel_a.xml", "actor|props/special/eyecandy/basket_celt_a.xml", "actor|props/special/eyecandy/crate_a.xml", "actor|props/special/eyecandy/dummy_a.xml", "actor|props/special/eyecandy/handcart_1.xml", "actor|props/special/eyecandy/handcart_1_broken.xml", "actor|props/special/eyecandy/sack_1.xml", "actor|props/special/eyecandy/sack_1_rough.xml"
     372    ]
     373)
     374{
     375    placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI));
     376    let quantity = randInt(5, 11);
     377    let dAngle = TWO_PI / quantity;
     378    for (let i = 0; i < quantity; ++i)
     379    {
     380        let angle = i * dAngle + randFloat(0, dAngle);
     381        let dist = randFloat(1, 3);
     382        placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), otherEntities[randInt(0, otherEntities.length - 1)], 0, randFloat(0, TWO_PI));
     383    }
     384}
     385
     386let foodEntities = ["gaia/flora_bush_berry", "gaia/fauna_chicken", "gaia/fauna_chicken"];
     387// Start loaction resources
     388function placeStartLocationResources(point)
     389{
     390    let currentAngle = randFloat(0, TWO_PI);
     391    // Stone and chicken
     392    let dAngle = TWO_PI * 2 / 9;
     393    let angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
     394    let dist = 12;
     395    let x = point.x + dist * Math.cos(angle);
     396    let y = point.y + dist * Math.sin(angle);
     397    placeMine({ "x": x, "y": y }, "gaia/geology_stonemine_temperate_quarry");
     398       
     399    currentAngle += dAngle;
     400   
     401    // Wood
     402    let quantity = 80;
     403    dAngle = TWO_PI / quantity / 3;
     404    for (let i = 0; i < quantity; ++i)
     405    {
     406        angle = currentAngle + randFloat(0, dAngle);
     407        dist = randFloat(10, 15);
     408        let objectList = groveEntities;
     409        if (i % 2 == 0)
     410            objectList = groveActors;
     411        x = point.x + dist * Math.cos(angle);
     412        y = point.y + dist * Math.sin(angle);
     413        placeObject(x, y, objectList[randInt(0, objectList.length - 1)], 0, randFloat(0, TWO_PI));
     414        createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter("temp_grass_plants"), paintClass(clGrove)]);
     415        currentAngle += dAngle;
     416    }
     417   
     418    // Metal and chicken
     419    dAngle = TWO_PI * 2 / 9;
     420    angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
     421    dist = 13;
     422    x = point.x + dist * Math.cos(angle);
     423    y = point.y + dist * Math.sin(angle);
     424    placeMine({ "x": x, "y": y }, "gaia/geology_metal_temperate_slabs");
     425    currentAngle += dAngle;
     426   
     427    // Berries
     428    quantity = 15;
     429    dAngle = TWO_PI / quantity * 2 / 9;
     430    for (let i = 0; i < quantity; ++i)
     431    {
     432        angle = currentAngle + randFloat(0, dAngle);
     433        dist = randFloat(10, 15);
     434        x = point.x + dist * Math.cos(angle);
     435        y = point.y + dist * Math.sin(angle);
     436        placeObject(x, y, foodEntities[randInt(0, foodEntities.length - 1)], 0, randFloat(0, TWO_PI));
     437        currentAngle += dAngle;
     438    }
     439}
     440
     441/**
     442 * Set height limits and water level by map size
     443 */
     444let heightScale = (g_Map.size + 256) / 768 / 4;
     445let heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale };
     446
     447let averageWaterCoverage = 1/5; // NOTE: Since terrain generation is quite unpredictable actual water coverage might vary much with the same value
     448let waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); // Water height in environment and the engine
     449let waterHeightAdjusted = waterHeight + MIN_HEIGHT; // Water height in RMGEN
     450setWaterHeight(waterHeight);
     451
     452
     453/**
     454 * Generate base terrain
     455 */
     456let medH = (heightRange.min + heightRange.max) / 2;
     457let initialHeightmap = [[medH, medH], [medH, medH]];
     458setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8);
     459
     460/**
     461 * Apply simple erosion
     462 */
     463// globalSmoothHeightmap(0.5);
     464for (let i = 0; i < 5; ++i)
     465    splashErodeMap(0.1);
     466
     467rescaleHeightmap(heightRange.min, heightRange.max);
     468
     469/**
     470 * Height presets
     471 */
     472let heighLimits = [
     473    heightRange.min + 1/3 * (waterHeightAdjusted - heightRange.min), // 0 Deep water
     474    heightRange.min + 2/3 * (waterHeightAdjusted - heightRange.min), // 1 Medium Water
     475    heightRange.min + (waterHeightAdjusted - heightRange.min), // 2 Shallow water
     476    waterHeightAdjusted + 1/8 * (heightRange.max - waterHeightAdjusted), // 3 Shore
     477    waterHeightAdjusted + 2/8 * (heightRange.max - waterHeightAdjusted), // 4 Low ground
     478    waterHeightAdjusted + 3/8 * (heightRange.max - waterHeightAdjusted), // 5 Player and path height
     479    waterHeightAdjusted + 4/8 * (heightRange.max - waterHeightAdjusted), // 6 High ground
     480    waterHeightAdjusted + 5/8 * (heightRange.max - waterHeightAdjusted), // 7 Lower forest border
     481    waterHeightAdjusted + 6/8 * (heightRange.max - waterHeightAdjusted), // 8 Forest
     482    waterHeightAdjusted + 7/8 * (heightRange.max - waterHeightAdjusted), // 9 Upper forest border
     483    waterHeightAdjusted + (heightRange.max - waterHeightAdjusted)]; // 10 Hilltop
     484
     485/**
     486 * Set environment
     487 */
     488setBiome(g_BiomeAlpine);
     489g_Environment.Fog.FogColor = { "r": 0.8, "g": 0.8, "b": 0.8, "a": 0.01 };
     490g_Environment.Water.WaterBody.Colour = { "r" : 0.3, "g" : 0.05, "b" : 0.1, "a" : 0.1 };
     491g_Environment.Water.WaterBody.Murkiness = 0.4;
     492
     493/**
     494 * Add tile painting presets
     495 */
     496let dummyActor = "actor|props/special/common/waypoint_flag.xml";
     497let myBiome = [];
     498myBiome.push({ // 0 Deep water
     499    "texture": ["shoreline_stoney_a"],
     500    "actor": [["gaia/fauna_fish", "actor|geology/stone_granite_boulder.xml"], 0.02],
     501    "textureHS": ["alpine_mountainside"], "actorHS": [["gaia/fauna_fish"], 0.1]
     502});
     503myBiome.push({ // 1 Medium Water
     504    "texture": ["shoreline_stoney_a", "alpine_shore_rocks"],
     505    "actor": [["actor|geology/stone_granite_boulder.xml", "actor|geology/stone_granite_med.xml"], 0.03],
     506    "textureHS": ["alpine_mountainside"], "actorHS": [["actor|geology/stone_granite_boulder.xml", "actor|geology/stone_granite_med.xml"], 0.0]
     507});
     508myBiome.push({ // 2 Shallow water
     509    "texture": ["alpine_shore_rocks"],
     510    "actor": [["actor|props/flora/reeds_pond_dry.xml", "actor|geology/stone_granite_large.xml", "actor|geology/stone_granite_med.xml", "actor|props/flora/reeds_pond_lush_b.xml"], 0.2],
     511    "textureHS": ["alpine_mountainside"], "actorHS": [["actor|props/flora/reeds_pond_dry.xml", "actor|geology/stone_granite_med.xml"], 0.1]
     512});
     513myBiome.push({ // 3 Shore
     514    "texture": ["alpine_shore_rocks_grass_50", "alpine_grass_rocky"],
     515    "actor": [["gaia/flora_tree_pine", "gaia/flora_bush_badlands", "actor|geology/highland1_moss.xml", "actor|props/flora/grass_soft_tuft_a.xml", "actor|props/flora/bush.xml"], 0.3],
     516    "textureHS": ["alpine_mountainside"], "actorHS": [["actor|props/flora/grass_soft_tuft_a.xml"], 0.1]
     517});
     518myBiome.push({ // 4 Low ground
     519    "texture": ["alpine_dirt_grass_50", "alpine_grass_rocky"],
     520    "actor": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_soft_tuft_a.xml", "actor|props/flora/bush.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2],
     521    "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_soft_tuft_a.xml"], 0.1]
     522});
     523myBiome.push({ // 5 Player and path height
     524    "texture": ["new_alpine_grass_c", "new_alpine_grass_b", "new_alpine_grass_d"],
     525    "actor": [["actor|geology/stone_granite_small.xml", "actor|props/flora/grass_soft_small.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2],
     526    "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_small.xml", "actor|props/flora/grass_soft_small.xml"], 0.1]
     527});
     528myBiome.push({ // 6 High ground
     529    "texture": ["new_alpine_grass_a", "alpine_grass_rocky"],
     530    "actor": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_tufts_a.xml", "actor|props/flora/bush_highlands.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2],
     531    "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_tufts_a.xml"], 0.1]
     532});
     533myBiome.push({ // 7 Lower forest border
     534    "texture": ["new_alpine_grass_mossy", "alpine_grass_rocky"],
     535    "actor": [["gaia/flora_tree_pine", "gaia/flora_tree_oak", "actor|props/flora/grass_tufts_a.xml", "gaia/flora_bush_berry", "actor|geology/highland2_moss.xml", "gaia/fauna_goat", "actor|props/flora/bush_tempe_underbrush.xml"], 0.3],
     536    "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|props/flora/grass_tufts_a.xml", "actor|geology/highland2_moss.xml"], 0.1]
     537});
     538myBiome.push({ // 8 Forest
     539    "texture": ["alpine_forrestfloor"],
     540    "actor": [["gaia/flora_tree_pine", "gaia/flora_tree_pine", "gaia/flora_tree_pine", "gaia/flora_tree_pine", "actor|geology/highland2_moss.xml", "actor|props/flora/bush_highlands.xml"], 0.5],
     541    "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|geology/highland2_moss.xml", "actor|geology/stone_granite_med.xml"], 0.1]
     542});
     543myBiome.push({ // 9 Upper forest border
     544    "texture": ["alpine_forrestfloor_snow", "new_alpine_grass_dirt_a"],
     545    "actor": [["gaia/flora_tree_pine", "actor|geology/snow1.xml"], 0.3],
     546    "textureHS": ["alpine_cliff_b"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|geology/snow1.xml"], 0.1]
     547});
     548myBiome.push({ // 10 Hilltop
     549    "texture": ["alpine_cliff_a", "alpine_cliff_snow"],
     550    "actor": [["actor|geology/highland1.xml"], 0.05],
     551    "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|geology/highland1.xml"], 0.0]
     552});
     553
     554
     555/**
     556 * Get start locations
     557 */
     558let startLocations = getStartLocationsByHeightmap({ "min": heighLimits[4], "max": heighLimits[5] }, 1000, 30);
     559// Sort start locations to form a "ring"
     560let startLocationOrder = getOrderOfPointsForShortestClosePath(startLocations);
     561let newStartLocations = [];
     562for (let i = 0; i < startLocations.length; ++i)
     563    newStartLocations.push(startLocations[startLocationOrder[i]]);
     564startLocations = newStartLocations;
     565// Sort players by team
     566let playerIDs = [];
     567let teams = [];
     568for (let i = 0; i < g_MapSettings.PlayerData.length - 1; ++i)
     569{
     570    playerIDs.push(i+1);
     571    let t = g_MapSettings.PlayerData[i + 1].Team;
     572    if (teams.indexOf(t) == -1 && t !== undefined)
     573        teams.push(t);
     574}
     575playerIDs = sortPlayers(playerIDs);
     576
     577// Minimize maximum distance between players within a team
     578if (teams.length)
     579{
     580    let minDistance = Infinity;
     581    let bestShift;
     582    for (let s = 0; s < playerIDs.length; ++s)
     583    {
     584        let maxTeamDist = 0;
     585        for (let pi = 0; pi < playerIDs.length - 1; ++pi)
     586        {
     587            let p1 = playerIDs[(pi + s) % playerIDs.length] - 1;
     588            let t1 = getPlayerTeam(p1);
     589            if (teams.indexOf(t1) === -1)
     590                continue;
     591            for (let pj = pi + 1; pj < playerIDs.length; ++pj)
     592            {
     593                let p2 = playerIDs[(pj + s) % playerIDs.length] - 1;
     594                let t2 = getPlayerTeam(p2);
     595                if (t2 != t1)
     596                    continue;
     597                let l1 = startLocations[pi];
     598                let l2 = startLocations[pj];
     599                let dist = getDistance(l1.x, l1.y, l2.x, l2.y);
     600                if (dist > maxTeamDist)
     601                    maxTeamDist = dist;
     602            }
     603        }
     604        if (maxTeamDist < minDistance)
     605        {
     606            minDistance = maxTeamDist;
     607            bestShift = s;
     608        }
     609    }
     610    if (bestShift)
     611    {
     612        let newPlayerIDs = [];
     613        for (let i = 0; i < playerIDs.length; ++i)
     614            newPlayerIDs.push(playerIDs[(i + bestShift) % playerIDs.length]);
     615        playerIDs = newPlayerIDs;
     616    }
     617}
     618
     619let playerHeight = (heighLimits[4] + heighLimits[5]) / 2;
     620
     621/**
     622 * Place start locations (resources later)
     623 */
     624for (let p = 0; p < playerIDs.length; ++p)
     625{
     626    let point = startLocations[p];
     627    rectangularSmoothToHeight(point, 35, 35, playerHeight, 0.7);
     628    placeCivDefaultEntities(point.x, point.y, playerIDs[p], { "iberWall": true });
     629}
     630
     631/**
     632 * Calculate tileCenteredHeightMap (This has nothing to to with TILE_CENTERED_HEIGHT_MAP which should be false (default) for this map to work properly)
     633 */
     634let tchm = getTileCenteredHeightmap();
     635
     636/**
     637 * Add paths class and area but don't paint before resource spots are chosen!
     638 */
     639let pathTerrainClassIDs = [];
     640for (let i = 0; i < startLocations.length; ++i)
     641{
     642    let start = startLocations[i];
     643    let target = startLocations[(i + 1) % startLocations.length];
     644    pathTerrainClassIDs.push(placeRandomPathToHeight(start, ["road_rome_a"], target, playerHeight, 8, 3, 0.1));
     645}
     646let pathArea = [];
     647for (let x = 0; x < tchm.length; ++x)
     648    for (let y = 0; y < tchm[0].length; ++y)
     649        for (let i = 0; i < pathTerrainClassIDs.length; ++i)
     650            if (getTileClass(pathTerrainClassIDs[i]).countMembersInRadius(x, y, 0.5))
     651                pathArea.push({ "x": x, "y": y });
     652
     653/**
     654 * Get resource spots after players start locations after path are calculated but before they are placed!
     655 */
     656let avoidPoints = deepcopy(startLocations);
     657for (let i = 0; i < avoidPoints.length; ++i)
     658    avoidPoints[i].dist = 30;
     659let resourceSpots = getPointsByHeight({ "min": (heighLimits[3] + heighLimits[4]) / 2, "max": (heighLimits[5] + heighLimits[6]) / 2 }, avoidPoints, pathArea);
     660
     661/**
     662 * Calculate slope map
     663 */
     664let slopeMap = getSlopeMap();
     665
     666/**
     667 * Divide tiles in areas by height and avoid paths
     668 */
     669let areas = [];
     670for (let h = 0; h < heighLimits.length; ++h)
     671    areas.push([]);
     672for (let x = 0; x < tchm.length; ++x)
     673{
     674    for (let y = 0; y < tchm[0].length; ++y)
     675    {
     676        let isPath = false;
     677        for (let i = 0; i < pathArea.length; ++i)
     678            if (pathArea[i].x == x && pathArea[i].y == y)
     679                isPath = true;
     680        if (isPath)
     681            continue;
     682       
     683        let minHeight = heightRange.min;
     684        for (let h = 0; h < heighLimits.length; ++h)
     685        {
     686            if (tchm[x][y] >= minHeight && tchm[x][y] <= heighLimits[h])
     687            {
     688                areas[h].push({ "x": x, "y": y });
     689                break;
     690            }
     691            else
     692                minHeight = heighLimits[h];
     693        }
     694    }
     695}
     696
     697/**
     698 * Get max slope of each area
     699 */
     700let minSlope = [];
     701let maxSlope = [];
     702for (let h = 0; h < heighLimits.length; ++h)
     703{
     704    minSlope[h] = Infinity;
     705    maxSlope[h] = 0;
     706    for (let t = 0; t < areas[h].length; ++t)
     707    {
     708        let x = areas[h][t].x;
     709        let y = areas[h][t].y;
     710        let slope = slopeMap[x][y];
     711        if (slope > maxSlope[h])
     712            maxSlope[h] = slope;
     713        if (slope < minSlope[h])
     714            minSlope[h] = slope;
     715    }
     716}
     717
     718/**
     719 * Paint areas by height and slope
     720 */
     721for (let h = 0; h < heighLimits.length; ++h)
     722{
     723    for (let t = 0; t < areas[h].length; ++t)
     724    {
     725        let x = areas[h][t].x;
     726        let y = areas[h][t].y;
     727        let actor = undefined;
     728       
     729        let texture = myBiome[h].texture[randInt(myBiome[h].texture.length)];
     730        if (slopeMap[x][y] < 0.4 * (minSlope[h] + maxSlope[h]))
     731        {
     732            if (randFloat() < myBiome[h].actor[1])
     733                actor = myBiome[h].actor[0][randInt(myBiome[h].actor[0].length)];
     734        }
     735        else
     736        {
     737            texture = myBiome[h].textureHS[randInt(myBiome[h].textureHS.length)];
     738            if (randFloat() < myBiome[h].actorHS[1])
     739                actor = myBiome[h].actorHS[0][randInt(myBiome[h].actorHS[0].length)];
     740        }
     741        g_Map.setTexture(x, y, texture);
     742        if (actor)
     743            placeObject(x + randFloat(), y + randFloat(), actor, 0, randFloat() * TWO_PI);
     744    }
     745}
     746
     747/**
     748 * Add starting resources after terrain texture painting
     749 */
     750for (let p = 0; p < g_MapSettings.PlayerData.length - 1; ++p)
     751    placeStartLocationResources(startLocations[p]);
     752
     753/**
     754 * Add resource spots after terrain texture painting
     755 */
     756for (let i = 0; i < resourceSpots.length; ++i)
     757{
     758    let choice = i % 5;
     759    if (choice == 0)
     760        placeMine(resourceSpots[i], "gaia/geology_stonemine_temperate_formation");
     761    if (choice == 1)
     762        placeMine(resourceSpots[i], "gaia/geology_metal_temperate_slabs");
     763    if (choice == 2)
     764        placeCustomFortress(resourceSpots[i].x, resourceSpots[i].y, fences[randInt(0, fences.length - 1)], "other", 0, randFloat(0, TWO_PI));
     765    if (choice == 3)
     766        placeGrove(resourceSpots[i]);
     767    if (choice == 4)
     768        placeCamp(resourceSpots[i]);
     769}
     770
     771/**
     772 * Stop Timer
     773 */
     774log("Map generation finished after " + ((new Date().getTime() - genStartTime) / 1000) + "s")
     775
     776/**
     777 * Export map data
     778 */
     779ExportMap();
  • binaries/data/mods/public/maps/random/caledonian_meadows.json

     
     1{
     2    "settings" : {
     3        "Name" : "Caledonian Meadows",
     4        "Script" : "caledonian_meadows.js",
     5        "Preview" : "caledonian_meadows.png",
     6        "Description" : "Fertile lands surrounded by harsh terrain.\nRemains of recent snowfalls still haunt the forests, and torrential rivers from the snowmelt barely died away.\nAiming at a realistic terrain, this map might not always be balanced.",
     7        "CircularMap" : false,
     8        "BaseTerrain" : "whiteness",
     9        "BaseHeight" : 0
     10    }
     11}