Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20462) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20463) @@ -1,516 +1,521 @@ const PI = Math.PI; const TWO_PI = 2 * Math.PI; const TERRAIN_SEPARATOR = "|"; const SEA_LEVEL = 20.0; const HEIGHT_UNITS_PER_METRE = 92; const MAP_BORDER_WIDTH = 3; const FALLBACK_CIV = "athen"; /** * Constants needed for heightmap_manipulation.js */ const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE; // Engine limit, Roughly 700 meters const MIN_HEIGHT = - SEA_LEVEL; const MAX_HEIGHT = MAX_HEIGHT_RANGE - SEA_LEVEL; // Default angle for buildings const BUILDING_ORIENTATION = - PI / 4; function fractionToTiles(f) { return g_Map.size * f; } function tilesToFraction(t) { return t / g_Map.size; } function fractionToSize(f) { return getMapArea() * f; } function sizeToFraction(s) { return s / getMapArea(); } function scaleByMapSize(min, max, minMapSize = 128, maxMapSize = 512) { return min + (max - min) * (g_Map.size - minMapSize) / (maxMapSize - minMapSize); } function cos(x) { return Math.cos(x); } function sin(x) { return Math.sin(x); } function abs(x) { return Math.abs(x); } function round(x) { return Math.round(x); } function lerp(a, b, t) { return a + (b-a) * t; } function sqrt(x) { return Math.sqrt(x); } function ceil(x) { return Math.ceil(x); } function floor(x) { return Math.floor(x); } function max(a, b) { return a > b ? a : b; } function min(a, b) { return a < b ? a : b; } /** * Retries the given function with those arguments as often as specified. */ function retryPlacing(placeFunc, retryFactor, amount, getResult, behaveDeprecated = false) { let maxFail = amount * retryFactor; let results = []; let good = 0; let bad = 0; while (good < amount && bad <= maxFail) { let result = placeFunc(); if (result !== undefined || behaveDeprecated) { ++good; if (getResult) results.push(result); } else ++bad; } return getResult ? results : good; } /** * Sets the x and z property of the given object (typically a Placer or Group) to a random point on the map. * @param passableOnly - Should be true for entity placement and false for terrain or elevation operations. */ function randomizeCoordinates(obj, passableOnly) { let border = passableOnly ? MAP_BORDER_WIDTH : 0; if (g_MapSettings.CircularMap) { // Polar coordinates // Uniformly distributed on the disk let halfMapSize = g_Map.size / 2 - border; let r = halfMapSize * Math.sqrt(randFloat(0, 1)); let theta = randFloat(0, 2 * Math.PI); obj.x = Math.floor(r * Math.cos(theta)) + halfMapSize; obj.z = Math.floor(r * Math.sin(theta)) + halfMapSize; } else { // Rectangular coordinates obj.x = randIntExclusive(border, g_Map.size - border); obj.z = randIntExclusive(border, g_Map.size - border); } } /** * Sets the x and z property of the given JS object (typically a Placer or Group) to a random point of the area. */ function randomizeCoordinatesFromAreas(obj, areas) { let pt = pickRandom(pickRandom(areas).points); obj.x = pt.x; obj.z = pt.z; } // TODO this is a hack to simulate the old behaviour of those functions // until all old maps are changed to use the correct version of these functions function createObjectGroupsDeprecated(group, player, constraint, amount, retryFactor = 10) { return createObjectGroups(group, player, constraint, amount, retryFactor, true); } function createObjectGroupsByAreasDeprecated(group, player, constraint, amount, retryFactor, areas) { return createObjectGroupsByAreas(group, player, constraint, amount, retryFactor, areas, true); } /** * Attempts to place the given number of areas in random places of the map. * Returns actually placed areas. */ function createAreas(centeredPlacer, painter, constraint, amount, retryFactor = 10) { let placeFunc = function() { randomizeCoordinates(centeredPlacer, false); return createArea(centeredPlacer, painter, constraint); }; return retryPlacing(placeFunc, retryFactor, amount, true, false); } /** * Attempts to place the given number of areas in random places of the given areas. * Returns actually placed areas. */ function createAreasInAreas(centeredPlacer, painter, constraint, amount, retryFactor, areas) { let placeFunc = function() { randomizeCoordinatesFromAreas(centeredPlacer, areas); return createArea(centeredPlacer, painter, constraint); }; return retryPlacing(placeFunc, retryFactor, amount, true, false); } /** * Attempts to place the given number of groups in random places of the map. * Returns the number of actually placed groups. */ function createObjectGroups(group, player, constraint, amount, retryFactor = 10, behaveDeprecated = false) { let placeFunc = function() { randomizeCoordinates(group, true); return createObjectGroup(group, player, constraint); }; return retryPlacing(placeFunc, retryFactor, amount, false, behaveDeprecated); } /** * Attempts to place the given number of groups in random places of the given areas. * Returns the number of actually placed groups. */ function createObjectGroupsByAreas(group, player, constraint, amount, retryFactor, areas, behaveDeprecated = false) { let placeFunc = function() { randomizeCoordinatesFromAreas(group, areas); return createObjectGroup(group, player, constraint); }; return retryPlacing(placeFunc, retryFactor, amount, false, behaveDeprecated); } function createTerrain(terrain) { if (!(terrain instanceof Array)) return createSimpleTerrain(terrain); return new RandomTerrain(terrain.map(t => createTerrain(t))); } function createSimpleTerrain(terrain) { if (typeof(terrain) != "string") throw new Error("createSimpleTerrain expects string as input, received " + uneval(terrain)); // Split string by pipe | character, this allows specifying terrain + tree type in single string let params = terrain.split(TERRAIN_SEPARATOR, 2); if (params.length != 2) return new SimpleTerrain(terrain); return new SimpleTerrain(params[0], params[1]); } function placeObject(x, z, type, player, angle) { if (g_Map.validT(x, z)) g_Map.addObject(new Entity(type, player, x, z, angle)); } function placeTerrain(x, z, terrainNames) { createTerrain(terrainNames).place(x, z); } function initTerrain(terrainNames) { let terrain = createTerrain(terrainNames); for (let x = 0; x < getMapSize(); ++x) for (let z = 0; z < getMapSize(); ++z) terrain.place(x, z); } function isCircularMap() { return !!g_MapSettings.CircularMap; } function getMapBaseHeight() { return g_MapSettings.BaseHeight; } function createTileClass() { return g_Map.createTileClass(); } function getTileClass(id) { if (!g_Map.validClass(id)) return undefined; return g_Map.tileClasses[id]; } /** * Constructs a new Area shaped by the Placer meeting the Constraint and calls the Painters there. * Supports both Centered and Non-Centered Placers. */ function createArea(placer, painter, constraint) { if (!constraint) constraint = new NullConstraint(); else if (constraint instanceof Array) constraint = new AndConstraint(constraint); let points = placer.place(constraint); if (!points) return undefined; let area = g_Map.createArea(points); if (painter instanceof Array) painter = new MultiPainter(painter); painter.paint(area); return area; } /** * @param mode is one of the HeightPlacer constants determining whether to exclude the min/max elevation. */ function paintTerrainBasedOnHeight(minHeight, maxHeight, mode, terrain) { createArea( new HeightPlacer(mode, minHeight, maxHeight), new TerrainPainter(terrain)); } function paintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileClass) { createArea( new HeightPlacer(mode, minHeight, maxHeight), new TileClassPainter(getTileClass(tileClass))); } function unPaintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileClass) { createArea( new HeightPlacer(mode, minHeight, maxHeight), new TileClassUnPainter(getTileClass(tileClass))); } /** * Places the Entities of the given Group if they meet the Constraint * and sets the given player as the owner. */ function createObjectGroup(group, player, constraint) { if (!constraint) constraint = new NullConstraint(); else if (constraint instanceof Array) constraint = new AndConstraint(constraint); return group.place(player, constraint); } function getMapSize() { return g_Map.size; } function getMapArea() { - return g_Map.size * g_Map.size; + return Math.square(g_Map.size); +} + +function getMapCenter() +{ + return new Vector2D(g_Map.size / 2, g_Map.size / 2); } function getNumPlayers() { return g_MapSettings.PlayerData.length - 1; } function getCivCode(player) { if (g_MapSettings.PlayerData[player+1].Civ) return g_MapSettings.PlayerData[player+1].Civ; warn("undefined civ specified for player " + (player + 1) + ", falling back to '" + FALLBACK_CIV + "'"); return FALLBACK_CIV; } function areAllies(player1, player2) { if (g_MapSettings.PlayerData[player1+1].Team === undefined || g_MapSettings.PlayerData[player2+1].Team === undefined || g_MapSettings.PlayerData[player2+1].Team == -1 || g_MapSettings.PlayerData[player1+1].Team == -1) return false; return g_MapSettings.PlayerData[player1+1].Team === g_MapSettings.PlayerData[player2+1].Team; } function getPlayerTeam(player) { if (g_MapSettings.PlayerData[player+1].Team === undefined) return -1; return g_MapSettings.PlayerData[player+1].Team; } function getHeight(x, z) { return g_Map.getHeight(x, z); } function setHeight(x, z, height) { g_Map.setHeight(x, z, height); } function initHeight(height) { g_Map.initHeight(height); } /** * Utility functions for classes */ /** * Add point to given class by id */ function addToClass(x, z, id) { let tileClass = getTileClass(id); if (tileClass !== null) tileClass.add(x, z); } /** * Remove point from the given class by id */ function removeFromClass(x, z, id) { let tileClass = getTileClass(id); if (tileClass !== null) tileClass.remove(x, z); } /** * Create a painter for the given class */ function paintClass(id) { return new TileClassPainter(getTileClass(id)); } /** * Create a painter for the given class */ function unPaintClass(id) { return new TileClassUnPainter(getTileClass(id)); } /** * Create an avoid constraint for the given classes by the given distances */ function avoidClasses(/*class1, dist1, class2, dist2, etc*/) { let ar = []; for (let i = 0; i < arguments.length/2; ++i) ar.push(new AvoidTileClassConstraint(arguments[2*i], arguments[2*i+1])); // Return single constraint if (ar.length == 1) return ar[0]; return new AndConstraint(ar); } /** * Create a stay constraint for the given classes by the given distances */ function stayClasses(/*class1, dist1, class2, dist2, etc*/) { let ar = []; for (let i = 0; i < arguments.length/2; ++i) ar.push(new StayInTileClassConstraint(arguments[2*i], arguments[2*i+1])); // Return single constraint if (ar.length == 1) return ar[0]; return new AndConstraint(ar); } /** * Create a border constraint for the given classes by the given distances */ function borderClasses(/*class1, idist1, odist1, class2, idist2, odist2, etc*/) { let ar = []; for (let i = 0; i < arguments.length/3; ++i) ar.push(new BorderTileClassConstraint(arguments[3*i], arguments[3*i+1], arguments[3*i+2])); // Return single constraint if (ar.length == 1) return ar[0]; return new AndConstraint(ar); } /** * Checks if the given tile is in class "id" */ function checkIfInClass(x, z, id) { let tileClass = getTileClass(id); if (tileClass === null) return 0; let members = tileClass.countMembersInRadius(x, z, 1); if (members === null) return 0; return members; } function getTerrainTexture(x, y) { return g_Map.getTexture(x, y); } Index: ps/trunk/binaries/data/mods/public/maps/random/survivalofthefittest.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/survivalofthefittest.js (revision 20462) +++ ps/trunk/binaries/data/mods/public/maps/random/survivalofthefittest.js (revision 20463) @@ -1,246 +1,203 @@ RMS.LoadLibrary("rmgen"); RMS.LoadLibrary("rmbiome"); setSelectedBiome(); const tMainTerrain = g_Terrains.mainTerrain; const tForestFloor1 = g_Terrains.forestFloor1; const tForestFloor2 = g_Terrains.forestFloor2; const tCliff = g_Terrains.cliff; const tHill = g_Terrains.hill; const tTier1Terrain = g_Terrains.tier1Terrain; const tTier2Terrain = g_Terrains.tier2Terrain; const tTier3Terrain = g_Terrains.tier3Terrain; const tTier4Terrain = g_Terrains.tier4Terrain; const oTree1 = g_Gaia.tree1; const oTree2 = g_Gaia.tree2; const oTree3 = g_Gaia.tree3; const oTree4 = g_Gaia.tree4; const oTree5 = g_Gaia.tree5; const aGrass = g_Decoratives.grass; const aGrassShort = g_Decoratives.grassShort; const aRockLarge = g_Decoratives.rockLarge; const aRockMedium = g_Decoratives.rockMedium; const aBushMedium = g_Decoratives.bushMedium; const aBushSmall = g_Decoratives.bushSmall; const aWaypointFlag = "actor|props/special/common/waypoint_flag.xml"; const pForest1 = [tForestFloor2 + TERRAIN_SEPARATOR + oTree1, tForestFloor2 + TERRAIN_SEPARATOR + oTree2, tForestFloor2]; const pForest2 = [tForestFloor1 + TERRAIN_SEPARATOR + oTree4, tForestFloor1 + TERRAIN_SEPARATOR + oTree5, tForestFloor1]; const oTreasureSeeker = "skirmish/units/default_support_female_citizen"; -const oCivicCenter = "skirmish/structures/default_civil_centre"; -const oCitizenInfantry = "skirmish/units/default_infantry_melee_b"; const triggerPointAttacker = "trigger/trigger_point_A"; const triggerPointTreasures = [ "trigger/trigger_point_B", "trigger/trigger_point_C", "trigger/trigger_point_D" ]; InitMap(); var numPlayers = getNumPlayers(); var mapSize = getMapSize(); +var mapArea = getMapArea(); +var mapCenter = getMapCenter(); var clPlayer = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clDirt = createTileClass(); var clBaseResource = createTileClass(); var clLand = createTileClass(); var clWomen = createTileClass(); initTerrain(tMainTerrain); -var ix = Math.round(fractionToTiles(0.5)); -var iz = Math.round(fractionToTiles(0.5)); - // Create the main treasure area in the middle of the map createArea( - new ClumpPlacer(mapSize * mapSize * scaleByMapSize(0.065, 0.09), 0.7, 0.1, 10, ix, iz), + new ClumpPlacer(mapArea * scaleByMapSize(0.065, 0.09), 0.7, 0.1, 10, mapCenter.x, mapCenter.y), [ new LayeredPainter([tMainTerrain, tMainTerrain], [3]), new SmoothElevationPainter(ELEVATION_SET, 3, 3), paintClass(clLand) - ], - null); + ]); RMS.SetProgress(10); var [playerIDs, playerX, playerZ, playerAngle, startAngle] = radialPlayerPlacement(0.3); +var [halfwayX, halfwayZ] = distributePointsOnCircle(numPlayers, startAngle, fractionToTiles(0.375), mapCenter.x, mapCenter.y); +var [attackerX, attackerZ] = distributePointsOnCircle(numPlayers, startAngle, fractionToTiles(0.45), mapCenter.x, mapCenter.y); +var [passageX, passageZ] = distributePointsOnCircle(numPlayers, startAngle + Math.PI / numPlayers, fractionToTiles(0.5), mapCenter.x, mapCenter.y); -var attackerX = []; -var attackerZ = []; - +log("Creating player bases and attacker points..."); for (let i = 0; i < numPlayers; ++i) { - attackerX[i] = 0.5 + 0.45*cos(playerAngle[i]); - attackerZ[i] = 0.5 + 0.45*sin(playerAngle[i]); -} - -for (let i = 0; i < numPlayers; ++i) -{ - var id = playerIDs[i]; - log("Creating base for player " + id + "..."); - - var radius = scaleByMapSize(15, 25); - - // place the attacker spawning trigger point - var ax = round(fractionToTiles(attackerX[i])); - var az = round(fractionToTiles(attackerZ[i])); - placeObject(ax, az, triggerPointAttacker, id, PI); - placeObject(ax, az, aWaypointFlag, 0, PI/2); - addToClass(ax, az, clPlayer); - addToClass(round(fractionToTiles((attackerX[i] + playerX[i]) / 2)), round(fractionToTiles((attackerZ[i] + playerZ[i]) / 2)), clPlayer); - - // get the x and z in tiles let fx = fractionToTiles(playerX[i]); let fz = fractionToTiles(playerZ[i]); - let ix = round(fx); - let iz = round(fz); - addCivicCenterAreaToClass(ix, iz, clPlayer); + placeStartingEntities(fx, fz, playerIDs[i], getStartingEntities(playerIDs[i] - 1).filter(ent => + ent.Template.indexOf("civil_centre") != -1 || ent.Template.indexOf("infantry") != -1)); - // Place default civ starting entities - var uDist = 6; - var uSpace = 2; - placeObject(fx, fz, oCivicCenter, id, BUILDING_ORIENTATION); - var uAngle = BUILDING_ORIENTATION - PI / 2; - var count = 4; - for (let numberofentities = 0; numberofentities < count; ++numberofentities) - { - var ux = fx + uDist * cos(uAngle) + numberofentities * uSpace * cos(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * cos(uAngle + PI/2)); - var uz = fz + uDist * sin(uAngle) + numberofentities * uSpace * sin(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * sin(uAngle + PI/2)); - placeObject(ux, uz, oCitizenInfantry, id, uAngle); - } - - placeDefaultDecoratives(fx, fz, aGrassShort, clBaseResource, radius); - - var tang = startAngle + (i + 0.5) * 2 * PI / numPlayers; - - var placer = new PathPlacer( - fractionToTiles(0.5), - fractionToTiles(0.5), - fractionToTiles(0.5 + 0.5 * Math.cos(tang)), - fractionToTiles(0.5 + 0.5 * Math.sin(tang)), - scaleByMapSize(14, 24), - 0.4, - 3 * scaleByMapSize(1, 3), - 0.2, - 0.05); + placeDefaultDecoratives(fx, fz, aGrassShort, clBaseResource, scaleByMapSize(15, 25)); + log("Creating passage separating players..."); createArea( - placer, + new PathPlacer(mapCenter.x, mapCenter.y, passageX[i], passageZ[i], scaleByMapSize(14, 24), 0.4, scaleByMapSize(3, 9), 0.2, 0.05), [ new LayeredPainter([tMainTerrain, tMainTerrain], [1]), new SmoothElevationPainter(ELEVATION_SET, 3, 4) - ], - null); + ]); - var femaleLocation = getTIPIADBON([ix, iz], [mapSize / 2, mapSize / 2], [-3 , 3.5], 1, 3); - if (femaleLocation !== undefined) - { - placeObject(femaleLocation[0], femaleLocation[1], oTreasureSeeker, id, playerAngle[i] + PI); - addToClass(floor(femaleLocation[0]), floor(femaleLocation[1]), clWomen); - } + log("Placing treasure seeker woman..."); + let femaleLocation = getTIPIADBON([fx, fz], [mapCenter.x, mapCenter.y], [-3 , 3.5], 1, 3); + placeObject(femaleLocation[0], femaleLocation[1], oTreasureSeeker, playerIDs[i], playerAngle[i] + Math.PI); + addToClass(Math.floor(femaleLocation[0]), Math.floor(femaleLocation[1]), clWomen); + + log("Placing attacker spawn point...."); + placeObject(attackerX[i], attackerZ[i], aWaypointFlag, 0, Math.PI / 2); + placeObject(attackerX[i], attackerZ[i], triggerPointAttacker, playerIDs[i], Math.PI / 2); + + log("Preventing mountains in the area between player and attackers..."); + addCivicCenterAreaToClass(Math.round(fx), Math.round(fz), clPlayer); + addToClass(Math.round(attackerX[i]), Math.round(attackerZ[i]), clPlayer); + addToClass(Math.round(halfwayX[i]), Math.round(halfwayZ[i]), clPlayer); } RMS.SetProgress(20); paintTerrainBasedOnHeight(3.12, 29, 1, tCliff); paintTileClassBasedOnHeight(3.12, 29, 1, clHill); for (let triggerPointTreasure of triggerPointTreasures) createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(triggerPointTreasure, 1, 1, 0, 0)], true, clWomen), 0, [avoidClasses(clForest, 5, clPlayer, 5, clHill, 5), stayClasses(clLand, 5)], scaleByMapSize(40, 140), 100 ); RMS.SetProgress(25); createBumps(stayClasses(clLand, 5)); var [forestTrees, stragglerTrees] = getTreeCounts(...rBiomeTreeCount(1)); createForests( [tMainTerrain, tForestFloor1, tForestFloor2, pForest1, pForest2], [avoidClasses(clPlayer, 20, clForest, 5, clHill, 0, clBaseResource,2, clWomen, 5), stayClasses(clLand, 4)], clForest, forestTrees); RMS.SetProgress(30); if (randBool()) createHills( [tMainTerrain, tCliff, tHill], [avoidClasses(clPlayer, 20, clHill, 5, clBaseResource, 3, clWomen, 5), stayClasses(clLand, 5)], clHill, scaleByMapSize(10, 60) * numPlayers); else createMountains( tCliff, [avoidClasses(clPlayer, 20, clHill, 5, clBaseResource, 3, clWomen, 5), stayClasses(clLand, 5)], clHill, scaleByMapSize(10, 60) * numPlayers); RMS.SetProgress(40); createHills( [tCliff, tCliff, tHill], avoidClasses(clPlayer, 20, clHill, 5, clBaseResource, 3, clWomen, 5, clLand, 5), clHill, scaleByMapSize(15, 90) * numPlayers, undefined, undefined, undefined, undefined, 55); RMS.SetProgress(50); log("Creating dirt patches..."); createLayeredPatches( [scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)], [[tMainTerrain, tTier1Terrain], [tTier1Terrain, tTier2Terrain], [tTier2Terrain, tTier3Terrain]], [1, 1], [avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12, clWomen, 5), stayClasses(clLand, 5)], scaleByMapSize(15, 45), clDirt); log("Creating grass patches..."); createPatches( [scaleByMapSize(2, 4), scaleByMapSize(3, 7), scaleByMapSize(5, 15)], tTier4Terrain, [avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12, clWomen, 5), stayClasses(clLand, 5)], scaleByMapSize(15, 45), clDirt); var planetm = 1; if (currentBiome() == "tropic") planetm = 8; 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), planetm * scaleByMapSize(13, 200), planetm * scaleByMapSize(13, 200), planetm * scaleByMapSize(13, 200) ], [avoidClasses(clForest, 0, clPlayer, 0, clHill, 0), stayClasses(clLand, 5)] ); createStragglerTrees( [oTree1, oTree2, oTree4, oTree3], [avoidClasses(clForest, 7, clHill, 1, clPlayer, 9), stayClasses(clLand, 7)], clForest, stragglerTrees); ExportMap();