0ad/binaries/data/mods/public/maps/random/schwarzwald.js
leper 8097eeb8ab Fix RMS library forgotten in the previous commit.
Changes some direct accesses to use the library function.

This was SVN commit r16100.
2015-01-01 23:57:00 +00:00

798 lines
29 KiB
JavaScript

// Created by Niek ten Brinke (aka niektb)
// Based on FeXoR's Daimond Square Algorithm for heightmap generation and several official random maps
'use strict';
RMS.LoadLibrary('rmgen');
// initialize map
log('Initializing map...');
InitMap();
////////////////
//
// Initializing
//
////////////////
//sky
setSkySet("fog");
setFogFactor(0.35);
setFogThickness(0.19);
// water
setWaterColour(0.501961, 0.501961, 0.501961);
setWaterTint(0.25098, 0.501961, 0.501961);
setWaterWaviness(0.5);
setWaterType("clap");
setWaterMurkiness(0.75);
// post processing
setPPSaturation(0.37);
setPPContrast(0.4);
setPPBrightness(0.4);
setPPEffect("hdr");
setPPBloom(0.4);
// Setup tile classes
var clPlayer = createTileClass();
var clPath = createTileClass();
var clHill = createTileClass();
var clForest = createTileClass();
var clWater = createTileClass();
var clRock = createTileClass();
var clFood = createTileClass();
var clBaseResource = createTileClass();
var clOpen = createTileClass();
// Setup Templates
var templateStone = 'gaia/geology_stone_alpine_a';
var templateStoneMine = 'gaia/geology_stonemine_alpine_quarry';
var templateMetal = 'actor|geology/stone_granite_med.xml';
var templateMetalMine = 'gaia/geology_metal_alpine_slabs';
var startingResources = ['gaia/flora_tree_pine', 'gaia/flora_tree_pine','gaia/flora_tree_pine', templateStoneMine,
'gaia/flora_bush_grapes', 'gaia/flora_tree_aleppo_pine','gaia/flora_tree_aleppo_pine','gaia/flora_tree_aleppo_pine', 'gaia/flora_bush_berry', templateMetalMine];
var aGrass = 'actor|props/flora/grass_soft_small_tall.xml';
var aGrassShort = 'actor|props/flora/grass_soft_large.xml';
var aRockLarge = 'actor|geology/stone_granite_med.xml';
var aRockMedium = 'actor|geology/stone_granite_med.xml';
var aBushMedium = 'actor|props/flora/bush_medit_me.xml';
var aBushSmall = 'actor|props/flora/bush_medit_sm.xml';
var aReeds = 'actor|props/flora/reeds_pond_lush_b.xml';
var oFish = "gaia/fauna_fish";
// Setup terrain
var terrainWood = ['alpine_forrestfloor|gaia/flora_tree_oak', 'alpine_forrestfloor|gaia/flora_tree_pine'];
var terrainWoodBorder = ['new_alpine_grass_mossy|gaia/flora_tree_oak', 'alpine_forrestfloor|gaia/flora_tree_pine',
'temp_grass_long|gaia/flora_bush_temperate', 'temp_grass_clovers|gaia/flora_bush_berry', 'temp_grass_clovers_2|gaia/flora_bush_grapes',
'temp_grass_plants|gaia/fauna_deer', 'temp_grass_plants|gaia/fauna_rabbit', 'new_alpine_grass_dirt_a'];
var terrainBase = ['temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants', 'temp_grass_plants|gaia/fauna_sheep'];
var terrainBaseBorder = ['temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_d', 'temp_grass_plants',
'temp_plants_bog', 'temp_grass_plants', 'temp_grass_plants'];
var terrainBaseCenter = ['temp_dirt_gravel_b'];
var baseTex = ['temp_road', 'temp_road_overgrown'];
var terrainPath = ['temp_road', 'temp_road_overgrown'];
var terrainHill = ['temp_highlands', 'temp_highlands', 'temp_highlands', 'temp_grass_plants_b', 'temp_cliff_a'];
var terrainHillBorder = ['temp_highlands', 'temp_highlands', 'temp_highlands', 'temp_grass_plants_b', 'temp_grass_plants_plants',
'temp_highlands', 'temp_highlands', 'temp_highlands', 'temp_grass_plants_b', 'temp_grass_plants_plants',
'temp_highlands', 'temp_highlands', 'temp_highlands', 'temp_cliff_b', 'temp_grass_plants_plants',
'temp_highlands', 'temp_highlands', 'temp_highlands', 'temp_cliff_b', 'temp_grass_plants_plants',
'temp_highlands|gaia/fauna_goat'];
var tWater = ['dirt_brown_d'];
var tWaterBorder = ['dirt_brown_d'];
const BUILDING_ANGlE = -PI/4;
// Setup map
var mapSize = getMapSize();
var mapRadius = mapSize/2;
var playableMapRadius = mapRadius - 5;
var mapCenterX = mapRadius;
var mapCenterZ = mapRadius;
// Setup players and bases
var numPlayers = getNumPlayers();
var baseRadius = 15;
var minPlayerRadius = min(mapRadius-1.5*baseRadius, 5*mapRadius/8);
var maxPlayerRadius = min(mapRadius-baseRadius, 3*mapRadius/4);
var playerStartLocX = new Array(numPlayers);
var playerStartLocZ = new Array(numPlayers);
var playerAngle = new Array(numPlayers);
var playerAngleStart = randFloat(0, 2*PI);
var playerAngleAddAvrg = 2*PI / numPlayers;
var playerAngleMaxOff = playerAngleAddAvrg/4;
// Setup paths
var pathSucsessRadius = baseRadius/2;
var pathAngleOff = PI/2;
var pathWidth = 10; // This is not really the path's thickness in tiles but the number of tiles in the clumbs of the path
// Setup additional resources
var resourceRadius = 2*mapRadius/3; // 3*mapRadius/8;
//var resourcePerPlayer = [templateStone, templateMetalMine];
// Setup woods
// For large maps there are memory errors with too many trees. A density of 256*192/mapArea works with 0 players.
// Around each player there is an area without trees so with more players the max density can increase a bit.
var maxTreeDensity = min(256 * (192 + 8 * numPlayers) / (mapSize * mapSize), 1); // Has to be tweeked but works ok
var bushChance = 1/3; // 1 means 50% chance in deepest wood, 0.5 means 25% chance in deepest wood
////////////////
//
// Some general functions
//
////////////////
function HeightPlacer(lowerBound, upperBound) {
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}
HeightPlacer.prototype.place = function (constraint) {
constraint = (constraint || new NullConstraint());
var ret = [];
for (var x = 0; x < g_Map.size; x++) {
for (var y = 0; y < g_Map.size; y++) {
if (g_Map.height[x][y] >= this.lowerBound && g_Map.height[x][y] <= this.upperBound && constraint.allows(x, y)) {
ret.push(new PointXZ(x, y));
}
}
}
return ret;
};
/*
Takes an array of 2D points (arrays of length 2)
Returns the order to go through the points for the shortest closed path (array of indices)
*/
function getOrderOfPointsForShortestClosePath(points)
{
var order = [];
var distances = [];
if (points.length <= 3)
{
for (var i = 0; i < points.length; i++)
order.push(i);
return order;
}
// Just add the first 3 points
var pointsToAdd = deepcopy(points);
for (var i = 0; i < min(points.length, 3); i++)
{
order.push(i);
pointsToAdd.shift(i);
if (i)
distances.push(getDistance(points[order[i]][0], points[order[i]][1], points[order[i - 1]][0], points[order[i - 1]][1]));
}
distances.push(getDistance(points[order[0]][0], points[order[0]][1], points[order[order.length - 1]][0], points[order[order.length - 1]][1]));
// Add remaining points so the path lengthens the least
var numPointsToAdd = pointsToAdd.length;
for (var i = 0; i < numPointsToAdd; i++)
{
var indexToAddTo = undefined;
var minEnlengthen = Infinity;
var minDist1 = 0;
var minDist2 = 0;
for (var k = 0; k < order.length; k++)
{
var dist1 = getDistance(pointsToAdd[0][0], pointsToAdd[0][1], points[order[k]][0], points[order[k]][1]);
var dist2 = getDistance(pointsToAdd[0][0], pointsToAdd[0][1], points[order[(k + 1) % order.length]][0], points[order[(k + 1) % order.length]][1]);
var enlengthen = dist1 + dist2 - distances[k];
if (enlengthen < minEnlengthen)
{
indexToAddTo = k;
minEnlengthen = enlengthen;
minDist1 = dist1;
minDist2 = dist2;
}
}
order.splice(indexToAddTo + 1, 0, i + 3);
distances.splice(indexToAddTo, 1, minDist1, minDist2);
pointsToAdd.shift();
}
return order;
}
////////////////
//
// Heightmap functionality
//
////////////////
// Some heightmap constants
const MIN_HEIGHT = - SEA_LEVEL; // -20
const MAX_HEIGHT = 0xFFFF/HEIGHT_UNITS_PER_METRE - SEA_LEVEL; // A bit smaller than 90
// Get the diferrence between minimum and maxumum height
function getMinAndMaxHeight(reliefmap)
{
var height = {};
height.min = Infinity;
height.max = - Infinity;
for (var x = 0; x < reliefmap.length; x++)
{
for (var y = 0; y < reliefmap[x].length; y++)
{
if (reliefmap[x][y] < height.min)
height.min = reliefmap[x][y];
else if (reliefmap[x][y] > height.max)
height.max = reliefmap[x][y];
}
}
return height;
}
function rescaleHeightmap(minHeight, maxHeight, heightmap)
{
minHeight = (minHeight || - SEA_LEVEL);
maxHeight = (maxHeight || 0xFFFF / HEIGHT_UNITS_PER_METRE - SEA_LEVEL);
heightmap = (heightmap || g_Map.height);
var oldHeightRange = getMinAndMaxHeight(heightmap);
var max_x = heightmap.length;
var max_y = heightmap[0].length;
for (var x = 0; x < max_x; x++)
for (var y = 0; y < max_y; y++)
heightmap[x][y] = minHeight + (heightmap[x][y] - oldHeightRange.min) / (oldHeightRange.max - oldHeightRange.min) * (maxHeight - minHeight);
}
/*
getStartLocationsByHeightmap
Takes
hightRange An associative array with keys 'min' and 'max' each containing a float (the height range start locations are allowed)
heightmap Optional, default is g_Map.height, an array of (map width) arrays of (map depth) floats
maxTries Optional, default is 1000, an integer, how often random player distributions are rolled to be compared
minDistToBorder Optional, default is 20, an integer, how far start locations have to be
numberOfPlayers Optional, default is getNumPlayers, an integer, how many start locations should be placed
Returns
An array of 2D points (arrays of length 2)
*/
function getStartLocationsByHeightmap(hightRange, maxTries, minDistToBorder, numberOfPlayers, heightmap)
{
maxTries = (maxTries || 1000);
minDistToBorder = (minDistToBorder || 20);
numberOfPlayers = (numberOfPlayers || getNumPlayers());
heightmap = (heightmap || g_Map.height);
var validStartLocTiles = [];
for (var x = minDistToBorder; x < heightmap.length - minDistToBorder; x++)
for (var y = minDistToBorder; y < heightmap[0].length - minDistToBorder; y++)
if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Has the right hight
validStartLocTiles.push([x, y]);
var maxMinDist = 0;
for (var tries = 0; tries < maxTries; tries++)
{
var startLoc = [];
var minDist = heightmap.length;
for (var p = 0; p < numberOfPlayers; p++)
startLoc.push(validStartLocTiles[randInt(validStartLocTiles.length)]);
for (var p1 = 0; p1 < numberOfPlayers - 1; p1++)
{
for (var p2 = p1 + 1; p2 < numberOfPlayers; p2++)
{
var dist = getDistance(startLoc[p1][0], startLoc[p1][1], startLoc[p2][0], startLoc[p2][1]);
if (dist < minDist)
minDist = dist;
}
}
if (minDist > maxMinDist)
{
maxMinDist = minDist;
var finalStartLoc = startLoc;
}
}
return finalStartLoc;
}
/*
derivateEntitiesByHeight
Takes
hightRange An associative array with keys 'min' and 'max' each containing a float (the height range start locations are allowed)
startLoc An array of 2D points (arrays of length 2)
heightmap Optional, default is g_Map.height, an array of (map width) arrays of (map depth) floats
entityList Array of entities/actors (strings to be placed with placeObject())
maxTries Optional, default is 1000, an integer, how often random player distributions are rolled to be compared
minDistance Optional, default is 30, an integer, how far start locations have to be away from start locations and the map border
Returns
An array of 2D points (arrays of length 2)
*/
function derivateEntitiesByHeight(hightRange, startLoc, entityList, maxTries, minDistance, heightmap)
{
entityList = (entityList || [templateMetalMine, templateStoneMine]);
maxTries = (maxTries || 1000);
minDistance = (minDistance || 40);
heightmap = (heightmap || g_Map.height);
var placements = deepcopy(startLoc);
var validTiles = [];
for (var x = minDistance; x < heightmap.length - minDistance; x++)
for (var y = minDistance; y < heightmap[0].length - minDistance; y++)
if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Has the right hight
validTiles.push([x, y]);
if (!validTiles.length)
return;
for (var tries = 0; tries < maxTries; tries++)
{
var tile = validTiles[randInt(validTiles.length)];
var isValid = true;
for (var p = 0; p < placements.length; p++)
{
if (getDistance(placements[p][0], placements[p][1], tile[0], tile[1]) < minDistance)
{
isValid = false;
break;
}
}
if (isValid)
{
placeObject(tile[0], tile[1], entityList[randInt(entityList.length)], 0, randFloat(0, 2*PI));
// placeObject(tile[0], tile[1], 'actor|geology/decal_stone_medit_b.xml', 0, randFloat(0, 2*PI));
placements.push(tile);
}
}
}
////////////////
//
// Base terrain generation functionality
//
////////////////
function setBaseTerrainDiamondSquare(minHeight, maxHeight, smoothness, initialHeightmap, heightmap)
{
// Make some arguments optional
minHeight = (minHeight || 0);
maxHeight = (maxHeight || 1);
var heightRange = maxHeight - minHeight;
if (heightRange <= 0)
warn('setBaseTerrainDiamondSquare: heightRange < 0');
smoothness = (smoothness || 1);
var offset = heightRange / 2;
initialHeightmap = (initialHeightmap || [[randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)], [randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)]]);
// Double initialHeightmap width untill target width is reached (diamond square method)
while (initialHeightmap.length < heightmap.length)
{
var newHeightmap = [];
var oldWidth = initialHeightmap.length;
// Square
for (var x = 0; x < 2 * oldWidth - 1; x++)
{
newHeightmap.push([]);
for (var y = 0; y < 2 * oldWidth - 1; y++)
{
if (x % 2 === 0 && y % 2 === 0) // Old tile
newHeightmap[x].push(initialHeightmap[x/2][y/2]);
else if (x % 2 == 1 && y % 2 == 1) // New tile with diagonal old tile neighbors
{
newHeightmap[x].push((initialHeightmap[(x-1)/2][(y-1)/2] + initialHeightmap[(x+1)/2][(y-1)/2] + initialHeightmap[(x-1)/2][(y+1)/2] + initialHeightmap[(x+1)/2][(y+1)/2]) / 4);
newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
}
else // New tile with straight old tile neighbors
newHeightmap[x].push(undefined); // Define later
}
}
// Diamond
for (var x = 0; x < 2 * oldWidth - 1; x++)
{
for (var y = 0; y < 2 * oldWidth - 1; y++)
{
if (newHeightmap[x][y] === undefined)
{
if (x > 0 && x + 1 < newHeightmap.length - 1 && y > 0 && y + 1 < newHeightmap.length - 1) // Not a border tile
{
newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 4;
newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
}
else if (x < newHeightmap.length - 1 && y > 0 && y < newHeightmap.length - 1) // Left border
{
newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x][y-1]) / 3;
newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
}
else if (x > 0 && y > 0 && y < newHeightmap.length - 1) // Right border
{
newHeightmap[x][y] = (newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3;
newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
}
else if (x > 0 && x < newHeightmap.length - 1 && y < newHeightmap.length - 1) // Bottom border
{
newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y]) / 3;
newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
}
else if (x > 0 && x < newHeightmap.length - 1 && y > 0) // Top border
{
newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3;
newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
}
}
}
}
initialHeightmap = deepcopy(newHeightmap);
offset /= Math.pow(2, smoothness);
}
// Cut initialHeightmap to fit target width
var shift = [floor((newHeightmap.length - heightmap.length) / 2), floor((newHeightmap[0].length - heightmap[0].length) / 2)];
for (var x = 0; x < heightmap.length; x++)
for (var y = 0; y < heightmap[0].length; y++)
heightmap[x][y] = newHeightmap[x][y];
}
////////////////
//
// Terrain erosion functionality
//
////////////////
function decayErrodeHeightmap(strength, heightmap)
{
strength = (strength || 0.9); // 0 to 1
heightmap = (heightmap || g_Map.height);
var referenceHeightmap = deepcopy(heightmap);
// var map = [[1, 0], [0, 1], [-1, 0], [0, -1]]; // faster
var map = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]; // smoother
var max_x = heightmap.length;
var max_y = heightmap[0].length;
for (var x = 0; x < max_x; x++)
for (var y = 0; y < max_y; y++)
for (var i = 0; i < map.length; i++)
heightmap[x][y] += strength / map.length * (referenceHeightmap[(x + map[i][0] + max_x) % max_x][(y + map[i][1] + max_y) % max_y] - referenceHeightmap[x][y]); // Not entirely sure if scaling with map.length is perfect but tested values seam to indicate it is
}
function rectangularSmoothToHeight(center, dx, dy, targetHeight, strength, heightmap)
{
var x = round(center[0]);
var y = round(center[1]);
dx = round(dx);
dy = round(dy);
strength = (strength || 1);
heightmap = (heightmap || g_Map.height);
var heightmapWin = [];
for (var wx = 0; wx < 2 * dx + 1; wx++)
{
heightmapWin.push([]);
for (var wy = 0; wy < 2 * dy + 1; wy++)
{
var actualX = x - dx + wx;
var actualY = y - dy + wy;
if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map
heightmapWin[wx].push(heightmap[actualX][actualY]);
else
heightmapWin[wx].push(targetHeight);
}
}
for (var wx = 0; wx < 2 * dx + 1; wx++)
{
for (var wy = 0; wy < 2 * dy + 1; wy++)
{
var actualX = x - dx + wx;
var actualY = y - dy + wy;
if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map
{
// Window function polynomial 2nd degree
var scaleX = 1 - (wx / dx - 1) * (wx / dx - 1);
var scaleY = 1 - (wy / dy - 1) * (wy / dy - 1);
heightmap[actualX][actualY] = heightmapWin[wx][wy] + strength * scaleX * scaleY * (targetHeight - heightmapWin[wx][wy]);
}
}
}
}
////////////////
//
// Actually do stuff
//
////////////////
////////////////
// Set height limits and water level by map size
////////////////
// Set target min and max height depending on map size to make average steepness about the same on all map sizes
var heightRange = {'min': MIN_HEIGHT * (g_Map.size + 512) / 1024, 'max': MAX_HEIGHT * (g_Map.size + 512) / 1024, 'avg': ((MIN_HEIGHT * (g_Map.size + 512) / 1024)+(MAX_HEIGHT * (g_Map.size + 512) / 1024))/2};
// Set average water coverage
var averageWaterCoverage = 1/5; // NOTE: Since erosion is not predictable actual water coverage might vary much with the same values
var waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min);
var waterHeightAdjusted = waterHeight + MIN_HEIGHT;
setWaterHeight(waterHeight);
////////////////
// Generate base terrain
////////////////
// Setting a 3x3 Grid as initial heightmap
var initialReliefmap = [[heightRange.max, heightRange.max, heightRange.max], [heightRange.max, heightRange.min, heightRange.max], [heightRange.max, heightRange.max, heightRange.max]];
setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, 0.5, initialReliefmap, g_Map.height);
// Apply simple erosion
for (var i = 0; i < 5; i++)
decayErrodeHeightmap(0.5);
rescaleHeightmap(heightRange.min, heightRange.max);
RMS.SetProgress(50);
//////////
// Setup height limit
//////////
// Height presets
var heighLimits = [
heightRange.min + 1/3 * (waterHeightAdjusted - heightRange.min), // 0 Deep water
heightRange.min + 2/3 * (waterHeightAdjusted - heightRange.min), // 1 Medium Water
heightRange.min + (waterHeightAdjusted - heightRange.min), // 2 Shallow water
waterHeightAdjusted + 1/8 * (heightRange.max - waterHeightAdjusted), // 3 Shore
waterHeightAdjusted + 2/8 * (heightRange.max - waterHeightAdjusted), // 4 Low ground
waterHeightAdjusted + 3/8 * (heightRange.max - waterHeightAdjusted), // 5 Player and path height
waterHeightAdjusted + 4/8 * (heightRange.max - waterHeightAdjusted), // 6 High ground
waterHeightAdjusted + 5/8 * (heightRange.max - waterHeightAdjusted), // 7 Lower forest border
waterHeightAdjusted + 6/8 * (heightRange.max - waterHeightAdjusted), // 8 Forest
waterHeightAdjusted + 7/8 * (heightRange.max - waterHeightAdjusted), // 9 Upper forest border
waterHeightAdjusted + (heightRange.max - waterHeightAdjusted)]; // 10 Hilltop
//////////
// Place start locations and apply terrain texture and decorative props
//////////
// Get start locations
var startLocations = getStartLocationsByHeightmap({'min': heighLimits[4], 'max': heighLimits[5]});
var playerHeight = (heighLimits[4] + heighLimits[5]) / 2;
for (var i=0; i < numPlayers; i++)
{
playerAngle[i] = (playerAngleStart + i*playerAngleAddAvrg + randFloat(0, playerAngleMaxOff))%(2*PI);
var x = round(mapCenterX + randFloat(minPlayerRadius, maxPlayerRadius)*cos(playerAngle[i]));
var z = round(mapCenterZ + randFloat(minPlayerRadius, maxPlayerRadius)*sin(playerAngle[i]));
playerStartLocX[i] = x;
playerStartLocZ[i] = z;
// Place starting entities
rectangularSmoothToHeight([x,z] , 20, 20, playerHeight, 0.8);
placeCivDefaultEntities(x, z, i+1, BUILDING_ANGlE, {'iberWall': false});
// Place base texture
var placer = new ClumpPlacer(2*baseRadius*baseRadius, 2/3, 1/8, 10, x, z);
var painter = [new TerrainPainter([baseTex], [baseRadius/4, baseRadius/4]), paintClass(clPlayer)];
createArea(placer, painter);
// Place starting resources
var distToSL = 15;
var resStartAngle = playerAngle[i] + PI;
var resAddAngle = 2*PI / startingResources.length;
for (var rIndex = 0; rIndex < startingResources.length; rIndex++)
{
var angleOff = randFloat(-resAddAngle/2, resAddAngle/2);
var placeX = x + distToSL*cos(resStartAngle + rIndex*resAddAngle + angleOff);
var placeZ = z + distToSL*sin(resStartAngle + rIndex*resAddAngle + angleOff);
placeObject(placeX, placeZ, startingResources[rIndex], 0, randFloat(0, 2*PI));
addToClass(round(placeX), round(placeZ), clBaseResource);
}
}
// Add further stone and metal mines
derivateEntitiesByHeight({'min': heighLimits[3], 'max': ((heighLimits[4]+heighLimits[3])/2)}, startLocations);
derivateEntitiesByHeight({'min': ((heighLimits[5]+heighLimits[6])/2), 'max': heighLimits[7]}, startLocations);
RMS.SetProgress(50);
//place water & open terrain textures and assign TileClasses
log("Painting textures...");
var placer = new HeightPlacer(heighLimits[2], (heighLimits[3]+heighLimits[2])/2);
var painter = new LayeredPainter([terrainBase, terrainBaseBorder], [5]);
createArea(placer, painter);
paintTileClassBasedOnHeight(heighLimits[2], (heighLimits[3]+heighLimits[2])/2, 1, clOpen);
var placer = new HeightPlacer(heightRange.min, heighLimits[2]);
var painter = new LayeredPainter([tWaterBorder, tWater], [2]);
createArea(placer, painter);
paintTileClassBasedOnHeight(heightRange.min, heighLimits[2], 1, clWater);
RMS.SetProgress(60);
// Place paths
log("Placing paths...");
var doublePaths = true;
if (numPlayers > 4)
doublePaths = false;
var doublePathMayPlayers = 4;
if (doublePaths === true)
var maxI = numPlayers+1;
else
var maxI = numPlayers;
for (var i = 0; i < maxI; i++)
{
if (doublePaths === true)
var minJ = 0;
else
var minJ = i+1;
for (var j = minJ; j < numPlayers+1; j++)
{
// Setup start and target coordinates
if (i < numPlayers)
{
var x = playerStartLocX[i];
var z = playerStartLocZ[i];
}
else
{
var x = mapCenterX;
var z = mapCenterZ;
}
if (j < numPlayers)
{
var targetX = playerStartLocX[j];
var targetZ = playerStartLocZ[j];
}
else
{
var targetX = mapCenterX;
var targetZ = mapCenterZ;
}
// Prepare path placement
var angle = getAngle(x, z, targetX, targetZ);
x += round(pathSucsessRadius*cos(angle));
z += round(pathSucsessRadius*sin(angle));
var targetReached = false;
var tries = 0;
// Placing paths
while (targetReached === false && tries < 2*mapSize)
{
var placer = new ClumpPlacer(pathWidth, 1, 1, 1, x, z);
var painter = [new TerrainPainter(terrainPath), new SmoothElevationPainter(ELEVATION_MODIFY, -0.1, 1.0), paintClass(clPath)];
createArea(placer, painter, avoidClasses(clPath, 0, clOpen, 0 ,clWater, 4, clBaseResource, 4));
// addToClass(x, z, clPath); // Not needed...
// Set vars for next loop
angle = getAngle(x, z, targetX, targetZ);
if (doublePaths === true) // Bended paths
{
x += round(cos(angle + randFloat(-pathAngleOff/2, 3*pathAngleOff/2)));
z += round(sin(angle + randFloat(-pathAngleOff/2, 3*pathAngleOff/2)));
}
else // Straight paths
{
x += round(cos(angle + randFloat(-pathAngleOff, pathAngleOff)));
z += round(sin(angle + randFloat(-pathAngleOff, pathAngleOff)));
}
if (getDistance(x, z, targetX, targetZ) < pathSucsessRadius)
targetReached = true;
tries++;
}
}
}
RMS.SetProgress(75);
//create general decoration
log("Creating decoration...");
createDecoration
(
[[new SimpleObject(aRockMedium, 1,3, 0,1)],
[new SimpleObject(aRockLarge, 1,2, 0,1), new SimpleObject(aRockMedium, 1,3, 0,2)],
[new SimpleObject(aGrassShort, 1,2, 0,1, -PI/8,PI/8)],
[new SimpleObject(aGrass, 2,4, 0,1.8, -PI/8,PI/8), new SimpleObject(aGrassShort, 3,6, 1.2,2.5, -PI/8,PI/8)],
[new SimpleObject(aBushMedium, 1,2, 0,2), new SimpleObject(aBushSmall, 2,4, 0,2)]
],
[
scaleByMapSize(16, 262),
scaleByMapSize(8, 131),
scaleByMapSize(13, 200),
scaleByMapSize(13, 200),
scaleByMapSize(13, 200)
],
avoidClasses(clForest, 1, clPlayer, 0, clPath, 3, clWater, 3)
);
RMS.SetProgress(80);
//create fish
log("Growing fish...");
createFood
(
[
[new SimpleObject(oFish, 2,3, 0,2)]
],
[
100 * numPlayers
],
[avoidClasses(clFood, 5), stayClasses(clWater, 4)]
);
RMS.SetProgress(85);
// create reeds
log("Planting reeds...");
var types = [aReeds]; // some variation
for (var i = 0; i < types.length; ++i)
{
var group = new SimpleGroup([new SimpleObject(types[i], 1,1, 0,0)], true);
createObjectGroups(group, 0,
borderClasses(clWater, 0, 6),
scaleByMapSize(960, 2000), 1000
);
}
RMS.SetProgress(90);
// place trees
log("Planting trees...");
for (var x = 0; x < mapSize; x++)
{
for (var z = 0;z < mapSize;z++)
{
// Some variables
var radius = Math.pow(Math.pow(mapCenterX - x - 0.5, 2) + Math.pow(mapCenterZ - z - 0.5, 2), 1/2); // The 0.5 is a correction for the entities placed on the center of tiles
var minDistToSL = mapSize;
for (var i=0; i < numPlayers; i++)
minDistToSL = min(minDistToSL, getDistance(playerStartLocX[i], playerStartLocZ[i], x, z));
// Woods tile based
var tDensFactSL = max(min((minDistToSL - baseRadius) / baseRadius, 1), 0);
var tDensFactRad = abs((resourceRadius - radius) / resourceRadius);
var tDensActual = (maxTreeDensity * tDensFactSL * tDensFactRad)*0.75;
if (randFloat() < tDensActual && radius < playableMapRadius)
{
if (tDensActual < bushChance*randFloat()*maxTreeDensity)
{
var placer = new ClumpPlacer(1, 1.0, 1.0, 1, x, z);
var painter = [new TerrainPainter(terrainWoodBorder), paintClass(clForest)];
createArea(placer, painter, avoidClasses(clPath, 1, clOpen, 2, clWater,3));
}
else
{
var placer = new ClumpPlacer(1, 1.0, 1.0, 1, x, z);
var painter = [new TerrainPainter(terrainWood), paintClass(clForest)];
createArea(placer, painter, avoidClasses(clPath, 2, clOpen, 3, clWater, 4));}
}
}
}
RMS.SetProgress(100);
// Export map data
ExportMap();