Ticket #4245: add_caledonian_meadows2016_9_24.diff

File add_caledonian_meadows2016_9_24.diff, 30.2 KB (added by FeXoR, 8 years ago)
  • 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 = new Array(max_x);
     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 = new Array(max_x);
     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 2D points (arrays of length 2), points that will be avoided in the given minDistance 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 = 30, 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            {
     269                validVertices.push({ "x": x, "y": y });
     270            }
     271        }
     272    }
     273   
     274    for (let tries = 0; tries < maxTries; ++tries)
     275    {
     276        let point = validVertices[randInt(validVertices.length)];
     277        if (placements.every(p => getDistance(p.x, p.y, point.x, point.y) > minDistance))
     278        {
     279            points.push(point);
     280            placements.push(point);
     281        }
     282    }
     283   
     284    return points;
     285}
     286
     287
     288/**
     289 * Design resource spots
     290 */
     291// Mines
     292let decorations = [
     293    "actor|geology/gray1.xml", "actor|geology/gray_rock1.xml",
     294    "actor|geology/highland1.xml", "actor|geology/highland2.xml", "actor|geology/highland3.xml",
     295    "actor|geology/highland_c.xml", "actor|geology/highland_d.xml", "actor|geology/highland_e.xml",
     296    "actor|props/flora/bush.xml", "actor|props/flora/bush_dry_a.xml", "actor|props/flora/bush_highlands.xml",
     297    "actor|props/flora/bush_tempe_a.xml", "actor|props/flora/bush_tempe_b.xml", "actor|props/flora/ferns.xml"
     298];
     299
     300function placeMine(point, centerEntity)
     301{
     302    placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI));
     303    let quantity = randInt(11, 23);
     304    let dAngle = TWO_PI / quantity;
     305    for (let i = 0; i < quantity; ++i)
     306    {
     307        let angle = i * dAngle + randFloat(0, dAngle);
     308        let dist = randFloat(2, 5);
     309        placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), decorations[randInt(0, decorations.length - 1)], 0, randFloat(0, TWO_PI));
     310    }
     311}
     312
     313// Food, fences with domestic animals
     314wallStyles["other"]["sheepIn"] = new WallElement("sheepIn", "gaia/fauna_sheep", PI / 4, -1.5, 0.75, PI/2);
     315wallStyles["other"]["foodBin"] = new WallElement("foodBin", "gaia/special_treasure_food_bin", PI/2, 1.5);
     316wallStyles["other"]["sheep"] = new WallElement("sheep", "gaia/fauna_sheep", 0, 0, 0.75);
     317wallStyles["other"]["farm"] = new WallElement("farm", "structures/brit_farmstead", PI, 0, -3);
     318let fences = [
     319    new Fortress("fence", ["foodBin", "farm", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence"]),
     320    new Fortress("fence", ["foodBin", "farm", "fence", "sheepIn", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence"]),
     321    new Fortress("fence", [
     322        "foodBin", "farm", "cornerIn", "bench", "cornerOut", "fence_short", "sheepIn", "fence", "sheepIn",
     323        "fence", "sheepIn", "fence_short", "sheep", "fence"
     324    ]),
     325    new Fortress("fence", [
     326        "foodBin", "farm", "cornerIn", "fence_short", "cornerOut", "bench", "sheepIn", "fence", "sheepIn",
     327        "fence", "sheepIn", "fence_short", "sheep", "fence"
     328    ]),
     329    new Fortress("fence", [
     330        "foodBin", "farm", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn",
     331        "fence_short", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence"
     332    ])
     333];
     334let num = fences.length;
     335for (let i = 0; i < num; ++i)
     336    fences.push(new Fortress("fence", deepcopy(fences[i].wall).reverse()));
     337
     338// Groves, only Wood
     339let groveEntities = [
     340    "gaia/flora_bush_temperate", "gaia/flora_tree_euro_beech"
     341];
     342let groveActors = [
     343    "actor|geology/highland1_moss.xml", "actor|geology/highland2_moss.xml",
     344    "actor|props/flora/bush.xml", "actor|props/flora/bush_dry_a.xml", "actor|props/flora/bush_highlands.xml",
     345    "actor|props/flora/bush_tempe_a.xml", "actor|props/flora/bush_tempe_b.xml", "actor|props/flora/ferns.xml"
     346];
     347let clGrove = createTileClass();
     348
     349function placeGrove(point)
     350{
     351    placeObject(point.x, point.y, ["structures/gaul_outpost", "gaia/flora_tree_oak_new"][randInt(0, 1)], 0, randFloat(0, TWO_PI));
     352    let quantity = randInt(20, 30);
     353    let dAngle = TWO_PI / quantity;
     354    for (let i = 0; i < quantity; ++i)
     355    {
     356        let angle = i * dAngle + randFloat(0, dAngle);
     357        let dist = randFloat(2, 5);
     358        let objectList = groveEntities;
     359        if (i % 3 == 0)
     360            objectList = groveActors;
     361        let x = point.x + dist * Math.cos(angle);
     362        let y = point.y + dist * Math.sin(angle);
     363        placeObject(x, y, objectList[randInt(0, objectList.length - 1)], 0, randFloat(0, TWO_PI));
     364        createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter("temp_grass_plants"), paintClass(clGrove)]);
     365    }
     366}
     367
     368let foodEntities = ["gaia/flora_bush_berry", "gaia/fauna_chicken", "gaia/fauna_chicken"];
     369// Start loaction resources
     370function placeStartLocationResources(point)
     371{
     372    let currentAngle = randFloat(0, TWO_PI);
     373    // Stone and chicken
     374    let dAngle = TWO_PI * 2 / 9;
     375    let angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
     376    let dist = 12;
     377    let x = point.x + dist * Math.cos(angle);
     378    let y = point.y + dist * Math.sin(angle);
     379    placeMine({ "x": x, "y": y }, "gaia/geology_stonemine_temperate_quarry");
     380       
     381    currentAngle += dAngle;
     382   
     383    // Wood
     384    let quantity = 80;
     385    dAngle = TWO_PI / quantity / 3;
     386    for (let i = 0; i < quantity; ++i)
     387    {
     388        angle = currentAngle + randFloat(0, dAngle);
     389        dist = randFloat(10, 15);
     390        let objectList = groveEntities;
     391        if (i % 2 == 0)
     392            objectList = groveActors;
     393        x = point.x + dist * Math.cos(angle);
     394        y = point.y + dist * Math.sin(angle);
     395        placeObject(x, y, objectList[randInt(0, objectList.length - 1)], 0, randFloat(0, TWO_PI));
     396        createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter("temp_grass_plants"), paintClass(clGrove)]);
     397        currentAngle += dAngle;
     398    }
     399   
     400    // Metal and chicken
     401    dAngle = TWO_PI * 2 / 9;
     402    angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4);
     403    dist = 13;
     404    x = point.x + dist * Math.cos(angle);
     405    y = point.y + dist * Math.sin(angle);
     406    placeMine({ "x": x, "y": y }, "gaia/geology_metal_temperate_slabs");
     407    currentAngle += dAngle;
     408   
     409    // Berries
     410    quantity = 15;
     411    dAngle = TWO_PI / quantity * 2 / 9;
     412    for (let i = 0; i < quantity; ++i)
     413    {
     414        angle = currentAngle + randFloat(0, dAngle);
     415        dist = randFloat(10, 15);
     416        x = point.x + dist * Math.cos(angle);
     417        y = point.y + dist * Math.sin(angle);
     418        placeObject(x, y, foodEntities[randInt(0, foodEntities.length - 1)], 0, randFloat(0, TWO_PI));
     419        currentAngle += dAngle;
     420    }
     421}
     422
     423/**
     424 * Set height limits and water level by map size
     425 */
     426let heightScale = (g_Map.size + 512) / 1024 / 4;
     427let heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale };
     428
     429let averageWaterCoverage = 1/5; // NOTE: Since terrain generation is quite unpredictable actual water coverage might vary much with the same value
     430let waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); // Water height in environment and the engine
     431let waterHeightAdjusted = waterHeight + MIN_HEIGHT; // Water height in RMGEN
     432setWaterHeight(waterHeight);
     433
     434
     435/**
     436 * Generate base terrain
     437 */
     438let medH = (heightRange.min + heightRange.max) / 2;
     439let initialHeightmap = [[medH, medH], [medH, medH]];
     440setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, undefined, 0.8);
     441
     442/**
     443 * Apply simple erosion
     444 */
     445// globalSmoothHeightmap(0.5);
     446for (let i = 0; i < 5; ++i)
     447    splashErodeMap(0.1);
     448
     449rescaleHeightmap(heightRange.min, heightRange.max);
     450
     451/**
     452 * Height presets
     453 */
     454let heighLimits = [
     455    heightRange.min + 1/3 * (waterHeightAdjusted - heightRange.min), // 0 Deep water
     456    heightRange.min + 2/3 * (waterHeightAdjusted - heightRange.min), // 1 Medium Water
     457    heightRange.min + (waterHeightAdjusted - heightRange.min), // 2 Shallow water
     458    waterHeightAdjusted + 1/8 * (heightRange.max - waterHeightAdjusted), // 3 Shore
     459    waterHeightAdjusted + 2/8 * (heightRange.max - waterHeightAdjusted), // 4 Low ground
     460    waterHeightAdjusted + 3/8 * (heightRange.max - waterHeightAdjusted), // 5 Player and path height
     461    waterHeightAdjusted + 4/8 * (heightRange.max - waterHeightAdjusted), // 6 High ground
     462    waterHeightAdjusted + 5/8 * (heightRange.max - waterHeightAdjusted), // 7 Lower forest border
     463    waterHeightAdjusted + 6/8 * (heightRange.max - waterHeightAdjusted), // 8 Forest
     464    waterHeightAdjusted + 7/8 * (heightRange.max - waterHeightAdjusted), // 9 Upper forest border
     465    waterHeightAdjusted + (heightRange.max - waterHeightAdjusted)]; // 10 Hilltop
     466
     467/**
     468 * Set environment
     469 */
     470setBiome(g_BiomeAlpine);
     471g_Environment.Fog.FogColor = { "r": 0.8, "g": 0.8, "b": 0.8, "a": 0.01 };
     472g_Environment.Water.WaterBody.Colour = { "r" : 0.3, "g" : 0.05, "b" : 0.1, "a" : 0.1 };
     473g_Environment.Water.WaterBody.Murkiness = 0.4;
     474
     475/**
     476 * Add tile painting presets
     477 */
     478let dummyActor = "actor|props/special/common/waypoint_flag.xml";
     479let myBiome = [];
     480myBiome.push({ // 0 Deep water
     481    "texture": ["shoreline_stoney_a"],
     482    "actor": [["gaia/fauna_fish", "actor|geology/stone_granite_boulder.xml"], 0.02],
     483    "textureHS": ["alpine_mountainside"], "actorHS": [["gaia/fauna_fish"], 0.1]
     484});
     485myBiome.push({ // 1 Medium Water
     486    "texture": ["shoreline_stoney_a", "alpine_shore_rocks"],
     487    "actor": [["actor|geology/stone_granite_boulder.xml", "actor|geology/stone_granite_med.xml"], 0.03],
     488    "textureHS": ["alpine_mountainside"], "actorHS": [["actor|geology/stone_granite_boulder.xml", "actor|geology/stone_granite_med.xml"], 0.0]
     489});
     490myBiome.push({ // 2 Shallow water
     491    "texture": ["alpine_shore_rocks"],
     492    "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],
     493    "textureHS": ["alpine_mountainside"], "actorHS": [["actor|props/flora/reeds_pond_dry.xml", "actor|geology/stone_granite_med.xml"], 0.1]
     494});
     495myBiome.push({ // 3 Shore
     496    "texture": ["alpine_shore_rocks_grass_50", "alpine_grass_rocky"],
     497    "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],
     498    "textureHS": ["alpine_mountainside"], "actorHS": [["actor|props/flora/grass_soft_tuft_a.xml"], 0.1]
     499});
     500myBiome.push({ // 4 Low ground
     501    "texture": ["alpine_dirt_grass_50", "alpine_grass_rocky"],
     502    "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],
     503    "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_soft_tuft_a.xml"], 0.1]
     504});
     505myBiome.push({ // 5 Player and path height
     506    "texture": ["new_alpine_grass_c", "new_alpine_grass_b", "new_alpine_grass_d"],
     507    "actor": [["actor|geology/stone_granite_small.xml", "actor|props/flora/grass_soft_small.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2],
     508    "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_small.xml", "actor|props/flora/grass_soft_small.xml"], 0.1]
     509});
     510myBiome.push({ // 6 High ground
     511    "texture": ["new_alpine_grass_a", "alpine_grass_rocky"],
     512    "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],
     513    "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_tufts_a.xml"], 0.1]
     514});
     515myBiome.push({ // 7 Lower forest border
     516    "texture": ["new_alpine_grass_mossy", "alpine_grass_rocky"],
     517    "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],
     518    "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|props/flora/grass_tufts_a.xml", "actor|geology/highland2_moss.xml"], 0.1]
     519});
     520myBiome.push({ // 8 Forest
     521    "texture": ["alpine_forrestfloor"],
     522    "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],
     523    "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|geology/highland2_moss.xml", "actor|geology/stone_granite_med.xml"], 0.1]
     524});
     525myBiome.push({ // 9 Upper forest border
     526    "texture": ["alpine_forrestfloor_snow", "new_alpine_grass_dirt_a"],
     527    "actor": [["gaia/flora_tree_pine", "actor|geology/snow1.xml"], 0.3],
     528    "textureHS": ["alpine_cliff_b"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|geology/snow1.xml"], 0.1]
     529});
     530myBiome.push({ // 10 Hilltop
     531    "texture": ["alpine_cliff_a", "alpine_cliff_snow"],
     532    "actor": [["actor|geology/highland1.xml"], 0.05],
     533    "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|geology/highland1.xml"], 0.0]
     534});
     535
     536
     537/**
     538 * Get start locations
     539 */
     540let startLocations = getStartLocationsByHeightmap({ "min": heighLimits[4], "max": heighLimits[5] }, 1000, 30);
     541// Sort start locations to form a "ring"
     542let startLocationOrder = getOrderOfPointsForShortestClosePath(startLocations);
     543let newStartLocations = [];
     544for (let i = 0; i < startLocations.length; ++i)
     545    newStartLocations.push(startLocations[startLocationOrder[i]]);
     546startLocations = newStartLocations;
     547// Sort players by team
     548let playerIDs = [];
     549let teams = [];
     550for (let i = 0; i < g_MapSettings.PlayerData.length - 1; ++i)
     551{
     552    playerIDs.push(i+1);
     553    let t = g_MapSettings.PlayerData[i + 1].Team;
     554    if (teams.indexOf(t) == -1 && t !== undefined)
     555        teams.push(t);
     556}
     557playerIDs = sortPlayers(playerIDs);
     558
     559// Minimize maximum distance between players within a team
     560if (teams.length)
     561{
     562    let minDistance = Infinity;
     563    let bestShift;
     564    for (let s = 0; s < playerIDs.length; ++s)
     565    {
     566        let maxTeamDist = 0;
     567        for (let pi = 0; pi < playerIDs.length - 1; ++pi)
     568        {
     569            let p1 = playerIDs[(pi + s) % playerIDs.length] - 1;
     570            let t1 = getPlayerTeam(p1);
     571            if (teams.indexOf(t1) === -1)
     572                continue;
     573            for (let pj = pi + 1; pj < playerIDs.length; ++pj)
     574            {
     575                let p2 = playerIDs[(pj + s) % playerIDs.length] - 1;
     576                let t2 = getPlayerTeam(p2);
     577                if (t2 != t1)
     578                    continue;
     579                let l1 = startLocations[pi];
     580                let l2 = startLocations[pj];
     581                let dist = getDistance(l1.x, l1.y, l2.x, l2.y);
     582                if (dist > maxTeamDist)
     583                    maxTeamDist = dist;
     584            }
     585        }
     586        if (maxTeamDist < minDistance)
     587        {
     588            minDistance = maxTeamDist;
     589            bestShift = s;
     590        }
     591    }
     592    if (bestShift)
     593    {
     594        let newPlayerIDs = [];
     595        for (let i = 0; i < playerIDs.length; ++i)
     596            newPlayerIDs.push(playerIDs[(i + bestShift) % playerIDs.length]);
     597        playerIDs = newPlayerIDs;
     598    }
     599}
     600
     601let playerHeight = (heighLimits[4] + heighLimits[5]) / 2;
     602
     603/**
     604 * Place start locations (resources later)
     605 */
     606for (let p = 0; p < playerIDs.length; ++p)
     607{
     608    let point = startLocations[p];
     609    rectangularSmoothToHeight(point, 35, 35, playerHeight, 0.7);
     610    placeCivDefaultEntities(point.x, point.y, playerIDs[p], { "iberWall": true });
     611}
     612
     613/**
     614 * Calculate tileCenteredHeightMap (This has nothing to to with TILE_CENTERED_HEIGHT_MAP which should be false (default) for this map to work properly)
     615 */
     616let tchm = getTileCenteredHeightmap();
     617
     618/**
     619 * Add paths class and area but don't paint before resource spots are chosen!
     620 */
     621let pathTerrainClassIDs = [];
     622for (let i = 0; i < startLocations.length; ++i)
     623{
     624    let start = startLocations[i];
     625    let target = startLocations[(i + 1) % startLocations.length];
     626    pathTerrainClassIDs.push(placeRandomPathToHeight(start, ["road_rome_a"], target, playerHeight, 8, 3, 0.1));
     627}
     628let pathArea = [];
     629for (let x = 0; x < tchm.length; ++x)
     630    for (let y = 0; y < tchm[0].length; ++y)
     631        for (let i = 0; i < pathTerrainClassIDs.length; ++i)
     632            if (getTileClass(pathTerrainClassIDs[i]).countMembersInRadius(x, y, 0.5))
     633                pathArea.push({ "x": x, "y": y });
     634
     635/**
     636 * Get resource spots after players start locations after path are calculated but before they are placed!
     637 */
     638let resourceSpots = getPointsByHeight({ "min": (heighLimits[3] + heighLimits[4]) / 2, "max": (heighLimits[5] + heighLimits[6]) / 2 }, startLocations, pathArea);
     639
     640/**
     641 * Calculate slope map
     642 */
     643let slopeMap = getSlopeMap();
     644
     645/**
     646 * Divide tiles in areas by height and avoid paths
     647 */
     648let areas = [];
     649for (let h = 0; h < heighLimits.length; ++h)
     650    areas.push([]);
     651for (let x = 0; x < tchm.length; ++x)
     652{
     653    for (let y = 0; y < tchm[0].length; ++y)
     654    {
     655        let isPath = false;
     656        for (let i = 0; i < pathArea.length; ++i)
     657            if (pathArea[i].x == x && pathArea[i].y == y)
     658                isPath = true;
     659        if (isPath)
     660            continue;
     661       
     662        let minHeight = heightRange.min;
     663        for (let h = 0; h < heighLimits.length; ++h)
     664        {
     665            if (tchm[x][y] >= minHeight && tchm[x][y] <= heighLimits[h])
     666            {
     667                areas[h].push({ "x": x, "y": y });
     668                break;
     669            }
     670            else
     671                minHeight = heighLimits[h];
     672        }
     673    }
     674}
     675
     676/**
     677 * Get max slope of each area
     678 */
     679let minSlope = new Array(areas.length);
     680let maxSlope = new Array(areas.length);
     681for (let h = 0; h < heighLimits.length; ++h)
     682{
     683    minSlope[h] = Infinity;
     684    maxSlope[h] = 0;
     685    for (let t = 0; t < areas[h].length; ++t)
     686    {
     687        let x = areas[h][t].x;
     688        let y = areas[h][t].y;
     689        let slope = slopeMap[x][y];
     690        if (slope > maxSlope[h])
     691            maxSlope[h] = slope;
     692        if (slope < minSlope[h])
     693            minSlope[h] = slope;
     694    }
     695}
     696
     697/**
     698 * Paint areas by height and slope
     699 */
     700for (let h = 0; h < heighLimits.length; ++h)
     701{
     702    for (let t = 0; t < areas[h].length; ++t)
     703    {
     704        let x = areas[h][t].x;
     705        let y = areas[h][t].y;
     706        let actor = undefined;
     707       
     708        let texture = myBiome[h].texture[randInt(myBiome[h].texture.length)];
     709        if (slopeMap[x][y] < 0.4 * (minSlope[h] + maxSlope[h]))
     710        {
     711            if (randFloat() < myBiome[h].actor[1])
     712                actor = myBiome[h].actor[0][randInt(myBiome[h].actor[0].length)];
     713        }
     714        else
     715        {
     716            texture = myBiome[h].textureHS[randInt(myBiome[h].textureHS.length)];
     717            if (randFloat() < myBiome[h].actorHS[1])
     718                actor = myBiome[h].actorHS[0][randInt(myBiome[h].actorHS[0].length)];
     719        }
     720        g_Map.setTexture(x, y, texture);
     721        if (actor)
     722            placeObject(x + randFloat(), y + randFloat(), actor, 0, randFloat() * TWO_PI);
     723    }
     724}
     725
     726/**
     727 * Add starting resources after terrain texture painting
     728 */
     729for (let p = 0; p < g_MapSettings.PlayerData.length - 1; ++p)
     730    placeStartLocationResources(startLocations[p]);
     731
     732/**
     733 * Add resource spots after terrain texture painting
     734 */
     735for (let i = 0; i < resourceSpots.length; ++i)
     736{
     737    let choice = i % 4;
     738    if (choice == 0)
     739        placeMine(resourceSpots[i], "gaia/geology_stonemine_temperate_formation");
     740    if (choice == 1)
     741        placeMine(resourceSpots[i], "gaia/geology_metal_temperate_slabs");
     742    if (choice == 2)
     743        placeCustomFortress(resourceSpots[i].x, resourceSpots[i].y, fences[randInt(0, fences.length - 1)], "other", 0, randFloat(0, TWO_PI));
     744    if (choice == 3)
     745        placeGrove(resourceSpots[i]);
     746}
     747
     748/**
     749 * Stop Timer
     750 */
     751log("Map generation finished after " + ((new Date().getTime() - genStartTime) / 1000) + "s")
     752
     753/**
     754 * Export map data
     755 */
     756ExportMap();
  • 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 sourrounded by harsh terrain.\nRemeins of recent snowfalls still haunt the forests and torrential rivers from the snowmelt barely died away.\nFocussing on realistic terrain this map might not always be fair.",
     7        "CircularMap" : false,
     8        "BaseTerrain" : "whiteness",
     9        "BaseHeight" : 0
     10    }
     11}