Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen2/setup.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen2/setup.js (revision 18199) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen2/setup.js (revision 18200) @@ -1,578 +1,578 @@ const g_Amounts = { "scarce": 0.2, "few": 0.5, "normal": 1, "many": 1.75, "tons": 3 }; const g_Mixes = { "same": 0, "similar": 0.1, "normal": 0.25, "varied": 0.5, "unique": 0.75 }; const g_Sizes = { "tiny": 0.5, "small": 0.75, "normal": 1, "big": 1.25, "huge": 1.5, }; const g_AllAmounts = Object.keys(g_Amounts); const g_AllMixes = Object.keys(g_Mixes); const g_AllSizes = Object.keys(g_Sizes); const g_DefaultTileClasses = [ "animals", "baseResource", "berries", "bluff", "bluffSlope", "dirt", "fish", "food", "forest", "hill", "land", "map", "metal", "mountain", "plateau", "player", "prop", "ramp", "rock", "settlement", "spine", "valley", "water" ]; var g_MapInfo; var g_TileClasses; var g_Forests; /** * Adds an array of elements to the map. */ function addElements(els) { for (var i = 0; i < els.length; ++i) { var stay = null; if (els[i].stay !== undefined) stay = els[i].stay; els[i].func( [avoidClasses.apply(null, els[i].avoid), stayClasses.apply(null, stay)], pickSize(els[i].sizes), pickMix(els[i].mixes), pickAmount(els[i].amounts) ); } } /** * Converts "amount" terms to numbers. */ function pickAmount(amounts) { var amount = amounts[randInt(amounts.length)]; if (amount in g_Amounts) return g_Amounts[amount]; return g_Mixes.normal; } /** * Converts "mix" terms to numbers. */ function pickMix(mixes) { var mix = mixes[randInt(mixes.length)]; if (mix in g_Mixes) return g_Mixes[mix]; return g_Mixes.normal; } /** * Converts "size" terms to numbers. */ function pickSize(sizes) { var size = sizes[randInt(sizes.length)]; if (size in g_Sizes) return g_Sizes[size]; return g_Sizes.normal; } /** * Paints the entire map with a single tile type. */ function resetTerrain(terrain, tc, elevation) { g_MapInfo.mapSize = getMapSize(); g_MapInfo.mapArea = g_MapInfo.mapSize * g_MapInfo.mapSize; g_MapInfo.centerOfMap = Math.floor(g_MapInfo.mapSize / 2); g_MapInfo.mapRadius = -PI / 4; var placer = new ClumpPlacer(g_MapInfo.mapArea, 1, 1, 1, g_MapInfo.centerOfMap, g_MapInfo.centerOfMap); var terrainPainter = new LayeredPainter([terrain], []); var elevationPainter = new SmoothElevationPainter(ELEVATION_SET, elevation, 1); createArea(placer, [terrainPainter, elevationPainter, paintClass(tc)], null); g_MapInfo.mapHeight = elevation; } /** * Chose starting locations for the given players. * * @param {string} type - "radial", "stacked", "stronghold", "random" * @param {number} distance - radial distance from the center of the map */ function addBases(type, distance, groupedDistance) { type = type || "radial"; distance = distance || 0.3; groupedDistance = groupedDistance || 0.05; var playerIDs = randomizePlayers(); var players = {}; switch(type) { case "line": players = placeLine(playerIDs, distance, groupedDistance); break; case "radial": players = placeRadial(playerIDs, distance); break; case "random": players = placeRandom(playerIDs); break; case "stronghold": players = placeStronghold(playerIDs, distance, groupedDistance); break; } return players; } /** * Create the base for a single player. * * @param {Object} - contains id, angle, x, z * @param {boolean} - Whether or not iberian gets starting walls */ function createBase(player, walls) { // Get the x and z in tiles var fx = fractionToTiles(player.x); var fz = fractionToTiles(player.z); var ix = round(fx); var iz = round(fz); addToClass(ix, iz, g_TileClasses.player); addToClass(ix + 5, iz, g_TileClasses.player); addToClass(ix, iz + 5, g_TileClasses.player); addToClass(ix - 5, iz, g_TileClasses.player); addToClass(ix, iz - 5, g_TileClasses.player); // Create starting units if ((walls || walls === undefined) && g_MapInfo.mapSize > 192) - placeCivDefaultEntities(fx, fz, player.id, g_MapInfo.mapRadius); + placeCivDefaultEntities(fx, fz, player.id); else - placeCivDefaultEntities(fx, fz, player.id, g_MapInfo.mapRadius, { 'iberWall': false }); + placeCivDefaultEntities(fx, fz, player.id, { 'iberWall': false }); // Create the city patch var cityRadius = scaleByMapSize(15, 25) / 3; var placer = new ClumpPlacer(PI * cityRadius * cityRadius, 0.6, 0.3, 10, ix, iz); var painter = new LayeredPainter([g_Terrains.roadWild, g_Terrains.road], [1]); createArea(placer, painter, null); // Create initial berry bushes at random angle var bbAngle = randFloat(0, TWO_PI); var bbDist = 10; var bbX = round(fx + bbDist * cos(bbAngle)); var bbZ = round(fz + bbDist * sin(bbAngle)); group = new SimpleGroup( [new SimpleObject(g_Gaia.fruitBush, 5, 5, 0, 3)], true, g_TileClasses.baseResource, bbX, bbZ ); createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 2)); // Create metal mine at a different angle var mAngle = bbAngle; while(abs(mAngle - bbAngle) < PI / 3) mAngle = randFloat(0, TWO_PI); var mDist = 12; var mX = round(fx + mDist * cos(mAngle)); var mZ = round(fz + mDist * sin(mAngle)); group = new SimpleGroup( [new SimpleObject(g_Gaia.metalLarge, 1, 1, 0, 0)], true, g_TileClasses.baseResource, mX, mZ ); createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 2)); // Create stone mine beside metal mAngle += randFloat(PI / 8, PI / 4); mX = round(fx + mDist * cos(mAngle)); mZ = round(fz + mDist * sin(mAngle)); group = new SimpleGroup( [new SimpleObject(g_Gaia.stoneLarge, 1, 1, 0, 2)], true, g_TileClasses.baseResource, mX, mZ ); createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 2)); // Create initial chicken for (var j = 0; j < 2; ++j) { for (var tries = 0; tries < 10; ++tries) { var aAngle = randFloat(0, TWO_PI); var aDist = 9; var aX = round(fx + aDist * cos(aAngle)); var aZ = round(fz + aDist * sin(aAngle)); var group = new SimpleGroup( [new SimpleObject(g_Gaia.chicken, 5, 5, 0, 2)], true, g_TileClasses.baseResource, aX, aZ ); if (createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 4))) break; } } var hillSize = PI * g_MapInfo.mapRadius * g_MapInfo.mapRadius; // Create starting trees var num = g_MapInfo.biome == g_BiomeSavanna ? 5 : 15; for (var tries = 0; tries < 10; ++tries) { var tAngle = randFloat(0, TWO_PI); var tDist = randFloat(12, 13); var tX = round(fx + tDist * cos(tAngle)); var tZ = round(fz + tDist * sin(tAngle)); group = new SimpleGroup( [new SimpleObject(g_Gaia.tree1, num, num, 1, 3)], false, g_TileClasses.baseResource, tX, tZ ); if (createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 4))) break; } // Create grass tufts var num = hillSize / 250; for (var j = 0; j < num; ++j) { var gAngle = randFloat(0, TWO_PI); var gDist = g_MapInfo.mapRadius - (5 + randInt(7)); var gX = round(fx + gDist * cos(gAngle)); var gZ = round(fz + gDist * sin(gAngle)); group = new SimpleGroup( [new SimpleObject(g_Decoratives.grassShort, 2, 5, 0, 1, -PI / 8, PI / 8)], false, g_TileClasses.baseResource, gX, gZ ); createObjectGroup(group, 0, avoidClasses(g_TileClasses.baseResource, 4)); } } /** * Return an array where each element is an array of playerIndices of a team. */ function getTeams(numPlayers) { // Group players by team var teams = []; for (var i = 0; i < numPlayers; ++i) { let team = getPlayerTeam(i); if (team == -1) continue; if (!teams[team]) teams[team] = []; teams[team].push(i+1); } // Players without a team get a custom index for (var i = 0; i < numPlayers; ++i) if (getPlayerTeam(i) == -1) teams.push([i+1]); // Remove unused indices return teams.filter(team => true); } /** * Chose a random pattern for placing the bases of the players. */ function randomStartingPositionPattern() { var formats = ["radial"]; // Enable stronghold if we have a few teams and a big enough map if (g_MapInfo.teams.length >= 2 && g_MapInfo.numPlayers >= 4 && g_MapInfo.mapSize >= 256) formats.push("stronghold"); // Enable random if we have enough teams or enough players on a big enough map if (g_MapInfo.mapSize >= 256 && (g_MapInfo.teams.length >= 3 || g_MapInfo.numPlayers > 4)) formats.push("random"); // Enable line if we have enough teams and players on a big enough map if (g_MapInfo.teams.length >= 2 && g_MapInfo.numPlayers >= 4 && g_MapInfo.mapSize >= 384) formats.push("line"); return { "setup": formats[randInt(formats.length)], "distance": randFloat(0.2, 0.35), "separation": randFloat(0.05, 0.1) }; } /** * Mix player indices but sort by team. * * @returns {Array} - every item is an array of player indices */ function randomizePlayers() { var playerIDs = []; for (var i = 0; i < g_MapInfo.numPlayers; ++i) playerIDs.push(i + 1); return sortPlayers(playerIDs); } /** * Place teams in a line-pattern. * * @param {Array} playerIDs - typically randomized indices of players of a single team * @param {number} distance - radial distance from the center of the map * @param {number} groupedDistance - distance between players * * @returns {Array} - contains id, angle, x, z for every player */ function placeLine(playerIDs, distance, groupedDistance) { var players = []; for (var i = 0; i < g_MapInfo.teams.length; ++i) { var safeDist = distance; if (distance + g_MapInfo.teams[i].length * groupedDistance > 0.45) safeDist = 0.45 - g_MapInfo.teams[i].length * groupedDistance; var teamAngle = g_MapInfo.startAngle + (i + 1) * TWO_PI / g_MapInfo.teams.length; // Create player base for (var p = 0; p < g_MapInfo.teams[i].length; ++p) { players[g_MapInfo.teams[i][p]] = { "id": g_MapInfo.teams[i][p], "angle": g_MapInfo.startAngle + (p + 1) * TWO_PI / g_MapInfo.teams[i].length, "x": 0.5 + (safeDist + p * groupedDistance) * cos(teamAngle), "z": 0.5 + (safeDist + p * groupedDistance) * sin(teamAngle) }; createBase(players[g_MapInfo.teams[i][p]], false); } } return players; } /** * Place players in a circle-pattern. * * @param {number} distance - radial distance from the center of the map */ function placeRadial(playerIDs, distance) { var players = new Array(g_MapInfo.numPlayers); for (var i = 0; i < g_MapInfo.numPlayers; ++i) { var angle = g_MapInfo.startAngle + i * TWO_PI / g_MapInfo.numPlayers; players[i] = { "id": playerIDs[i], "angle": angle, "x": 0.5 + distance * cos(angle), "z": 0.5 + distance * sin(angle) }; createBase(players[i]); } return players; } /** * Chose arbitrary starting locations. */ function placeRandom(playerIDs) { var players = []; var placed = []; for (var i = 0; i < g_MapInfo.numPlayers; ++i) { var attempts = 0; var playerAngle = randFloat(0, TWO_PI); var distance = randFloat(0, 0.42); var x = 0.5 + distance * cos(playerAngle); var z = 0.5 + distance * sin(playerAngle); var tooClose = false; for (var j = 0; j < placed.length; ++j) { var sep = getDistance(x, z, placed[j].x, placed[j].z); if (sep < 0.25) { tooClose = true; break; } } if (tooClose) { --i; ++attempts; // Reset if we're in what looks like an infinite loop if (attempts > 100) { players = []; placed = []; i = -1; attempts = 0; } continue; } players[i] = { "id": playerIDs[i], "angle": playerAngle, "x": x, "z": z }; placed.push(players[i]); } // Create the bases for (var i = 0; i < g_MapInfo.numPlayers; ++i) createBase(players[i]); return players; } /** * Place given players in a stronghold-pattern. * * @param distance - radial distance from the center of the map * @param groupedDistance - distance between neighboring players */ function placeStronghold(playerIDs, distance, groupedDistance) { var players = []; for (var i = 0; i < g_MapInfo.teams.length; ++i) { var teamAngle = g_MapInfo.startAngle + (i + 1) * TWO_PI / g_MapInfo.teams.length; var fractionX = 0.5 + distance * cos(teamAngle); var fractionZ = 0.5 + distance * sin(teamAngle); // If we have a team of above average size, make sure they're spread out if (g_MapInfo.teams[i].length > 4) groupedDistance = randFloat(0.08, 0.12); // If we have a team of below average size, make sure they're together if (g_MapInfo.teams[i].length < 3) groupedDistance = randFloat(0.04, 0.06); // If we have a solo player, place them on the center of the team's location if (g_MapInfo.teams[i].length == 1) groupedDistance = 0; // Create player base for (var p = 0; p < g_MapInfo.teams[i].length; ++p) { var angle = g_MapInfo.startAngle + (p + 1) * TWO_PI / g_MapInfo.teams[i].length; players[g_MapInfo.teams[i][p]] = { "id": g_MapInfo.teams[i][p], "angle": angle, "x": fractionX + groupedDistance * cos(angle), "z": fractionZ + groupedDistance * sin(angle) }; createBase(players[g_MapInfo.teams[i][p]], false); } } return players; } /** * Creates tileClass for the default classes and every class given. * * @param {Array} newClasses * @returns {Object} - maps from classname to ID */ function initTileClasses(newClasses) { var classNames = g_DefaultTileClasses; if (newClasses !== undefined) classNames = classNames.concat(newClasses); g_TileClasses = {}; for (var className of classNames) g_TileClasses[className] = createTileClass(); } /** * Get biome-specific names of entities and terrain after randomization. */ function initBiome() { g_Forests = { "forest1": [ g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree1, g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree2, g_Terrains.forestFloor2 ], "forest2": [ g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree4, g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree5, g_Terrains.forestFloor1 ] }; } /** * Creates an object of commonly used functions. */ function initMapSettings() { initBiome(); let numPlayers = getNumPlayers(); g_MapInfo = { "biome": g_BiomeID, "numPlayers": numPlayers, "teams": getTeams(numPlayers), "startAngle": randFloat(0, TWO_PI) }; }