Index: ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/belgian_uplands.js (revision 20331) @@ -1,445 +1,436 @@ // Prepare progress calculation var timeArray = []; timeArray.push(Date.now()); // Importing rmgen libraries RMS.LoadLibrary("rmgen"); RMS.LoadLibrary("heightmap"); InitMap(); var numPlayers = getNumPlayers(); var mapSize = getMapSize(); // Function to apply a heightmap function setReliefmap(reliefmap) { // g_Map.height = reliefmap; for (var x = 0; x <= mapSize; x++) for (var y = 0; y <= mapSize; y++) setHeight(x, y, reliefmap[x][y]); } // Set target min and max height depending on map size to make average stepness the same on all map sizes var heightRange = {"min": MIN_HEIGHT * mapSize / 8192, "max": MAX_HEIGHT * mapSize / 8192}; // Set average water coverage var averageWaterCoverage = 1/3; // NOTE: Since errosion is not predictable actual water coverage might differ much with the same value if (mapSize < 200) // Sink the waterlevel on tiny maps to ensure enough space averageWaterCoverage = 2/3 * averageWaterCoverage; var waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); var waterHeightAdjusted = waterHeight + MIN_HEIGHT; setWaterHeight(waterHeight); ////////// // Prepare terrain texture by height placement ////////// var textueByHeight = []; // Deep water textueByHeight.push({"upperHeightLimit": heightRange.min + 1/3 * (waterHeightAdjusted - heightRange.min), "terrain": "temp_sea_rocks"}); // Medium deep water (with fish) var terrains = ["temp_sea_weed"]; terrains = terrains.concat(terrains, terrains, terrains, terrains); terrains = terrains.concat(terrains, terrains, terrains, terrains); terrains.push("temp_sea_weed|gaia/fauna_fish"); textueByHeight.push({"upperHeightLimit": heightRange.min + 2/3 * (waterHeightAdjusted - heightRange.min), "terrain": terrains}); // Flat Water textueByHeight.push({"upperHeightLimit": heightRange.min + 3/3 * (waterHeightAdjusted - heightRange.min), "terrain": "temp_mud_a"}); // Water surroundings/bog (with stone/metal some rabits and bushes) var terrains = ["temp_plants_bog", "temp_plants_bog_aut", "temp_dirt_gravel_plants", "temp_grass_d"]; terrains = terrains.concat(terrains, terrains, terrains, terrains, terrains); terrains = ["temp_plants_bog|gaia/flora_bush_temperate"].concat(terrains, terrains); terrains = ["temp_dirt_gravel_plants|gaia/geology_metal_temperate", "temp_dirt_gravel_plants|gaia/geology_stone_temperate", "temp_plants_bog|gaia/fauna_rabbit"].concat(terrains, terrains); terrains = ["temp_plants_bog_aut|gaia/flora_tree_dead"].concat(terrains, terrains); textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 1/6 * (heightRange.max - waterHeightAdjusted), "terrain": terrains}); // Juicy grass near bog textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 2/6 * (heightRange.max - waterHeightAdjusted), "terrain": ["temp_grass", "temp_grass_d", "temp_grass_long_b", "temp_grass_plants"]}); // Medium level grass // var testActor = "actor|geology/decal_stone_medit_a.xml"; textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 3/6 * (heightRange.max - waterHeightAdjusted), "terrain": ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_mossy"]}); // Long grass near forest border textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 4/6 * (heightRange.max - waterHeightAdjusted), "terrain": ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_d", "temp_grass_long_b", "temp_grass_clovers_2", "temp_grass_mossy", "temp_grass_plants"]}); // Forest border (With wood/food plants/deer/rabits) var terrains = ["temp_grass_plants|gaia/flora_tree_euro_beech", "temp_grass_mossy|gaia/flora_tree_poplar", "temp_grass_mossy|gaia/flora_tree_poplar_lombardy", "temp_grass_long|gaia/flora_bush_temperate", "temp_mud_plants|gaia/flora_bush_temperate", "temp_mud_plants|gaia/flora_bush_badlands", "temp_grass_long|gaia/flora_tree_apple", "temp_grass_clovers|gaia/flora_bush_berry", "temp_grass_clovers_2|gaia/flora_bush_grapes", "temp_grass_plants|gaia/fauna_deer", "temp_grass_long_b|gaia/fauna_rabbit"]; var numTerrains = terrains.length; for (var i = 0; i < numTerrains; i++) terrains.push("temp_grass_plants"); textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 5/6 * (heightRange.max - waterHeightAdjusted), "terrain": terrains}); // Unpassable woods textueByHeight.push({"upperHeightLimit": waterHeightAdjusted + 6/6 * (heightRange.max - waterHeightAdjusted), "terrain": ["temp_grass_mossy|gaia/flora_tree_oak", "temp_forestfloor_pine|gaia/flora_tree_pine", "temp_grass_mossy|gaia/flora_tree_oak", "temp_forestfloor_pine|gaia/flora_tree_pine", "temp_mud_plants|gaia/flora_tree_dead", "temp_plants_bog|gaia/flora_tree_oak_large", "temp_dirt_gravel_plants|gaia/flora_tree_aleppo_pine", "temp_forestfloor_autumn|gaia/flora_tree_carob"]}); var minTerrainDistToBorder = 3; // Time check 1 timeArray.push(Date.now()); RMS.SetProgress(5); // START THE GIANT WHILE LOOP: // - Generate Heightmap // - Search valid start position tiles // - Choose a good start position derivation (largest distance between closest players) // - Restart the loop if start positions are invalid or two players are to close to each other var goodStartPositionsFound = false; var minDistBetweenPlayers = 16 + mapSize / 16; // Don't set this higher than 25 for tiny maps! It will take forever with 8 players! var enoughTiles = false; var tries = 0; while (!goodStartPositionsFound) { tries++; log("Starting giant while loop try " + tries); // Generate reliefmap var myReliefmap = clone(g_Map.height); setRandomHeightmap(heightRange.min, heightRange.max, myReliefmap); for (var i = 0; i < 50 + mapSize/4; i++) // Cycles depend on mapsize (more cycles -> bigger structures) globalSmoothHeightmap(0.8, myReliefmap); rescaleHeightmap(heightRange.min, heightRange.max, myReliefmap); setReliefmap(myReliefmap); // Find good start position tiles var possibleStartPositions = []; var neededDistance = 7; var distToBorder = 2 * neededDistance; // Has to be greater than neededDistance! Otherwise the check if low/high ground is near will fail... var lowerHeightLimit = textueByHeight[3].upperHeightLimit; var upperHeightLimit = textueByHeight[6].upperHeightLimit; // Check for valid points by height for (var x = distToBorder + minTerrainDistToBorder; x < mapSize - distToBorder - minTerrainDistToBorder; x++) for (var y = distToBorder + minTerrainDistToBorder; y < mapSize - distToBorder - minTerrainDistToBorder; y++) { var actualHeight = getHeight(x, y); if (actualHeight > lowerHeightLimit && actualHeight < upperHeightLimit) { // Check for points within a valid area by height (rectangular since faster) var isPossible = true; for (var offX = - neededDistance; offX <= neededDistance; offX++) for (var offY = - neededDistance; offY <= neededDistance; offY++) { var testHeight = getHeight(x + offX, y + offY); if (testHeight <= lowerHeightLimit || testHeight >= upperHeightLimit) { isPossible = false; break; } } if (isPossible) possibleStartPositions.push([x, y]); // placeTerrain(x, y, "blue"); // For debug reasons. Plz don't remove. // Only works properly for 1 loop } } // Trying to reduce the number of possible start locations... // Reduce to tiles in a circle of mapSize / 2 distance to the center (to avoid players placed in corners) var possibleStartPositionsTemp = []; var maxDistToCenter = mapSize / 2; for (var i = 0; i < possibleStartPositions.length; i++) { - var deltaX = possibleStartPositions[i][0] - mapSize / 2; - var deltaY = possibleStartPositions[i][1] - mapSize / 2; - var distToCenter = Math.pow(Math.pow(deltaX, 2) + Math.pow(deltaY, 2), 1/2); - - if (distToCenter < maxDistToCenter) + if (Math.euclidDistance2D(...possibleStartPositions[i], mapSize / 2, mapSize / 2) < maxDistToCenter) possibleStartPositionsTemp.push(possibleStartPositions[i]); // placeTerrain(possibleStartPositions[i][0], possibleStartPositions[i][1], "purple"); // Only works properly for 1 loop } possibleStartPositions = clone(possibleStartPositionsTemp); - // Reduce to tiles near low and high ground (Rectangular check since faster) to make sure each player has access to all resource types. var possibleStartPositionsTemp = []; var maxDistToResources = distToBorder; // Has to be <= distToBorder! var minNumLowTiles = 10; var minNumHighTiles = 10; for (var i = 0; i < possibleStartPositions.length; i++) { var numLowTiles = 0; var numHighTiles = 0; for (var dx = - maxDistToResources; dx < maxDistToResources; dx++) { for (var dy = - maxDistToResources; dy < maxDistToResources; dy++) { var testHeight = getHeight(possibleStartPositions[i][0] + dx, possibleStartPositions[i][1] + dy); if (testHeight < lowerHeightLimit) numLowTiles++; if (testHeight > upperHeightLimit) numHighTiles++; if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles) break; } if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles) break; } if (numLowTiles > minNumLowTiles && numHighTiles > minNumHighTiles) possibleStartPositionsTemp.push(possibleStartPositions[i]); // placeTerrain(possibleStartPositions[i][0], possibleStartPositions[i][1], "red"); // Only works properly for 1 loop } possibleStartPositions = clone(possibleStartPositionsTemp); if(possibleStartPositions.length > numPlayers) enoughTiles = true; else { enoughTiles = false; log("possibleStartPositions.length < numPlayers, possibleStartPositions.length = " + possibleStartPositions.length + ", numPlayers = " + numPlayers); } // Find a good start position derivation if (enoughTiles) { // Get some random start location derivations. NOTE: Iterating over all possible derivations is just too much (valid points * numPlayers) - var maxTries = 100000; // floor(800000 / (Math.pow(numPlayers, 2) / 2)); + var maxTries = 100000; var possibleDerivations = []; for (var i = 0; i < maxTries; i++) { var vector = []; for (var p = 0; p < numPlayers; p++) vector.push(randIntExclusive(0, possibleStartPositions.length)); possibleDerivations.push(vector); } // Choose the start location derivation with the greatest minimum distance between players var maxMinDist = 0; for (var d = 0; d < possibleDerivations.length; d++) { var minDist = 2 * mapSize; for (var p1 = 0; p1 < numPlayers - 1; p1++) { for (var p2 = p1 + 1; p2 < numPlayers; p2++) { if (p1 != p2) { - var StartPositionP1 = possibleStartPositions[possibleDerivations[d][p1]]; - var StartPositionP2 = possibleStartPositions[possibleDerivations[d][p2]]; - var actualDist = Math.pow(Math.pow(StartPositionP1[0] - StartPositionP2[0], 2) + Math.pow(StartPositionP1[1] - StartPositionP2[1], 2), 1/2); - if (actualDist < minDist) - minDist = actualDist; + minDist = Math.min(minDist, Math.euclidDistance2D(...possibleStartPositions[possibleDerivations[d][p1]], ...possibleStartPositions[possibleDerivations[d][p2]])); if (minDist < maxMinDist) break; } } if (minDist < maxMinDist) break; } if (minDist > maxMinDist) { maxMinDist = minDist; var bestDerivation = possibleDerivations[d]; } } if (maxMinDist > minDistBetweenPlayers) { goodStartPositionsFound = true; log("Exiting giant while loop after " + tries + " tries with a minimum player distance of " + maxMinDist); } else log("maxMinDist <= " + minDistBetweenPlayers + ", maxMinDist = " + maxMinDist); } // End of derivation check } // END THE GIANT WHILE LOOP // Time check 2 timeArray.push(Date.now()); RMS.SetProgress(60); //////// // Paint terrain by height and add props //////// var propDensity = 1; // 1 means as determined in the loop, less for large maps as set below if (mapSize > 500) propDensity = 1/4; else if (mapSize > 400) propDensity = 3/4; for(var x = minTerrainDistToBorder; x < mapSize - minTerrainDistToBorder; x++) { for (var y = minTerrainDistToBorder; y < mapSize - minTerrainDistToBorder; y++) { var textureMinHeight = heightRange.min; for (var i = 0; i < textueByHeight.length; i++) { if (getHeight(x, y) >= textureMinHeight && getHeight(x, y) <= textueByHeight[i].upperHeightLimit) { placeTerrain(x, y, textueByHeight[i].terrain); // Add some props at... if (i == 0) // ...deep water { if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/pond_lillies_large.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/water_lillies.xml", 0, randFloat(0, 2*PI)); } if (i == 1) // ...medium water (with fish) { if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/pond_lillies_large.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/water_lillies.xml", 0, randFloat(0, 2*PI)); } if (i == 2) // ...low water/mud { if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/water_log.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/water_lillies.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|geology/highland_c.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 20)) placeObject(x, y, "actor|props/flora/reeds_pond_lush_b.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 10)) placeObject(x, y, "actor|props/flora/reeds_pond_lush_a.xml", 0, randFloat(0, 2*PI)); } if (i == 3) // ...water suroundings/bog { if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/water_log.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|geology/highland_c.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/reeds_pond_lush_a.xml", 0, randFloat(0, 2*PI)); } if (i == 4) // ...low height grass { if (randBool(propDensity / 800)) placeObject(x, y, "actor|props/flora/grass_field_flowering_tall.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 400)) placeObject(x, y, "actor|geology/gray_rock1.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/bush_tempe_sm_lush.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/bush_tempe_b.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/grass_soft_small_tall.xml", 0, randFloat(0, 2*PI)); } if (i == 5) // ...medium height grass { if (randBool(propDensity / 800)) placeObject(x, y, "actor|geology/decal_stone_medit_a.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 400)) placeObject(x, y, "actor|props/flora/decals_flowers_daisies.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/bush_tempe_underbrush.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/grass_soft_small_tall.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/grass_temp_field.xml", 0, randFloat(0, 2*PI)); } if (i == 6) // ...high height grass { if (randBool(propDensity / 400)) placeObject(x, y, "actor|geology/stone_granite_boulder.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/foliagebush.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/bush_tempe_underbrush.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/grass_soft_small_tall.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 20)) placeObject(x, y, "actor|props/flora/ferns.xml", 0, randFloat(0, 2*PI)); } if (i == 7) // ...forest border (with wood/food plants/deer/rabits) { if (randBool(propDensity / 400)) placeObject(x, y, "actor|geology/highland_c.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 200)) placeObject(x, y, "actor|props/flora/bush_tempe_a.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/ferns.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/grass_soft_tuft_a.xml", 0, randFloat(0, 2*PI)); } if (i == 8) // ...woods { if (randBool(propDensity / 200)) placeObject(x, y, "actor|geology/highland2_moss.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 100)) placeObject(x, y, "actor|props/flora/grass_soft_tuft_a.xml", 0, randFloat(0, 2*PI)); else if (randBool(propDensity / 40)) placeObject(x, y, "actor|props/flora/ferns.xml", 0, randFloat(0, 2*PI)); } break; } else textureMinHeight = textueByHeight[i].upperHeightLimit; } } } // Time check 3 timeArray.push(Date.now()); RMS.SetProgress(90); //////// // Place players and start resources //////// for (var p = 0; p < numPlayers; p++) { var actualX = possibleStartPositions[bestDerivation[p]][0]; var actualY = possibleStartPositions[bestDerivation[p]][1]; placeCivDefaultEntities(actualX, actualY, p + 1, { "iberWall": false }); // Place some start resources var uDist = 8; var uSpace = 1; for (var j = 1; j <= 4; ++j) { var uAngle = BUILDING_ORIENTATION - PI * (2-j) / 2; var count = 4; for (var numberofentities = 0; numberofentities < count; numberofentities++) { var ux = actualX + uDist * cos(uAngle) + numberofentities * uSpace * cos(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * cos(uAngle + PI/2)); var uz = actualY + uDist * sin(uAngle) + numberofentities * uSpace * sin(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * sin(uAngle + PI/2)); if (j % 2 == 0) placeObject(ux, uz, "gaia/flora_bush_berry", 0, randFloat(0, 2*PI)); else placeObject(ux, uz, "gaia/flora_tree_cypress", 0, randFloat(0, 2*PI)); } } } ExportMap(); // Time check 7 timeArray.push(Date.now()); // Calculate progress percentage with the time checks var generationTime = timeArray[timeArray.length - 1] - timeArray[0]; log("Total generation time (ms): " + generationTime); for (var i = 0; i < timeArray.length; i++) { var timeSinceStart = timeArray[i] - timeArray[0]; var progressPercentage = 100 * timeSinceStart / generationTime; log("Time check " + i + ": Progress (%): " + progressPercentage); } Index: ps/trunk/binaries/data/mods/public/maps/random/caledonian_meadows.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/caledonian_meadows.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/caledonian_meadows.js (revision 20331) @@ -1,532 +1,532 @@ RMS.LoadLibrary("rmgen"); RMS.LoadLibrary("rmbiome"); RMS.LoadLibrary("heightmap"); InitMap(); let genStartTime = Date.now(); /** * Drags a path to a target height smoothing it at the edges and return some points along the path. */ function placeRandomPathToHeight( start, target, targetHeight, tileClass = undefined, texture = "road_rome_a", width = 10, distance = 4, strength = 0.08, heightmap = g_Map.height) { let pathPoints = []; let position = clone(start); while (true) { rectangularSmoothToHeight(position, width, width, targetHeight, strength, heightmap); if (texture) { + let painters = [new TerrainPainter(texture)]; if (tileClass !== undefined) - createArea(new ClumpPlacer(0.3 * width * width, 1, 1, 1, floor(position.x), floor(position.y)), - [new TerrainPainter(texture), paintClass(tileClass)]); - else - createArea(new ClumpPlacer(0.3 * width * width, 1, 1, 1, floor(position.x), floor(position.y)), - new TerrainPainter(texture)); + painters.push(paintClass(tileClass)); + createArea( + new ClumpPlacer(0.3 * Math.square(width), 1, 1, 1, Math.floor(position.x), Math.floor(position.y)), + painters); } pathPoints.push({ "x": position.x, "y": position.y, "dist": distance }); // Check for distance to target and setup for next loop if needed - if (getDistance(position.x, position.y, target.x, target.y) < distance / 2) + if (Math.euclidDistance2D(position.x, position.y, target.x, target.y) < distance / 2) break; let angleToTarget = getAngle(position.x, position.y, target.x, target.y); let angleOff = randFloat(-PI/2, PI/2); position.x += distance * cos(angleToTarget + angleOff); position.y += distance * sin(angleToTarget + angleOff); } return pathPoints; } /** * Design resource spots */ // Mines let decorations = [ "actor|geology/gray1.xml", "actor|geology/gray_rock1.xml", "actor|geology/highland1.xml", "actor|geology/highland2.xml", "actor|geology/highland3.xml", "actor|geology/highland_c.xml", "actor|geology/highland_d.xml", "actor|geology/highland_e.xml", "actor|props/flora/bush.xml", "actor|props/flora/bush_dry_a.xml", "actor|props/flora/bush_highlands.xml", "actor|props/flora/bush_tempe_a.xml", "actor|props/flora/bush_tempe_b.xml", "actor|props/flora/ferns.xml" ]; function placeMine(point, centerEntity) { placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI)); let quantity = randIntInclusive(11, 23); let dAngle = TWO_PI / quantity; for (let i = 0; i < quantity; ++i) { let angle = dAngle * randFloat(i, i + 1); let dist = randFloat(2, 5); placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(decorations), 0, randFloat(0, 2 * PI)); } } // Food, fences with domestic animals wallStyles.other.sheepIn = new WallElement("sheepIn", "gaia/fauna_sheep", PI / 4, -1.5, 0.75, PI/2); wallStyles.other.foodBin = new WallElement("foodBin", "gaia/special_treasure_food_bin", PI/2, 1.5); wallStyles.other.sheep = new WallElement("sheep", "gaia/fauna_sheep", 0, 0, 0.75); wallStyles.other.farm = new WallElement("farm", "structures/brit_farmstead", PI, 0, -3); let fences = [ new Fortress("fence", ["foodBin", "farm", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence"]), new Fortress("fence", ["foodBin", "farm", "fence", "sheepIn", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence"]), new Fortress("fence", [ "foodBin", "farm", "cornerIn", "bench", "cornerOut", "fence_short", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence_short", "sheep", "fence" ]), new Fortress("fence", [ "foodBin", "farm", "cornerIn", "fence_short", "cornerOut", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence_short", "sheep", "fence" ]), new Fortress("fence", [ "foodBin", "farm", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence" ]) ]; let num = fences.length; for (let i = 0; i < num; ++i) fences.push(new Fortress("fence", clone(fences[i].wall).reverse())); // Groves, only Wood let groveEntities = ["gaia/flora_bush_temperate", "gaia/flora_tree_euro_beech"]; let groveActors = [ "actor|geology/highland1_moss.xml", "actor|geology/highland2_moss.xml", "actor|props/flora/bush.xml", "actor|props/flora/bush_dry_a.xml", "actor|props/flora/bush_highlands.xml", "actor|props/flora/bush_tempe_a.xml", "actor|props/flora/bush_tempe_b.xml", "actor|props/flora/ferns.xml" ]; let clGrove = createTileClass(); function placeGrove(point) { placeObject(point.x, point.y, pickRandom(["structures/gaul_outpost", "gaia/flora_tree_oak_new"]), 0, randFloat(0, 2 * PI)); let quantity = randIntInclusive(20, 30); let dAngle = TWO_PI / quantity; for (let i = 0; i < quantity; ++i) { let angle = dAngle * randFloat(i, i + 1); let dist = randFloat(2, 5); let objectList = groveEntities; if (i % 3 == 0) objectList = groveActors; let x = point.x + dist * Math.cos(angle); let y = point.y + dist * Math.sin(angle); placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * PI)); createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter("temp_grass_plants"), paintClass(clGrove)]); } } // Camps with fire and gold treasure function placeCamp(point, centerEntity = "actor|props/special/eyecandy/campfire.xml", otherEntities = ["gaia/special_treasure_metal", "gaia/special_treasure_standing_stone", "units/brit_infantry_slinger_b", "units/brit_infantry_javelinist_b", "units/gaul_infantry_slinger_b", "units/gaul_infantry_javelinist_b", "units/gaul_champion_fanatic", "actor|props/special/common/waypoint_flag.xml", "actor|props/special/eyecandy/barrel_a.xml", "actor|props/special/eyecandy/basket_celt_a.xml", "actor|props/special/eyecandy/crate_a.xml", "actor|props/special/eyecandy/dummy_a.xml", "actor|props/special/eyecandy/handcart_1.xml", "actor|props/special/eyecandy/handcart_1_broken.xml", "actor|props/special/eyecandy/sack_1.xml", "actor|props/special/eyecandy/sack_1_rough.xml" ] ) { placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI)); let quantity = randIntInclusive(5, 11); let dAngle = TWO_PI / quantity; for (let i = 0; i < quantity; ++i) { let angle = dAngle * randFloat(i, i + 1); let dist = randFloat(1, 3); placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(otherEntities), 0, randFloat(0, 2 * PI)); } } function placeStartLocationResources(point, foodEntities = ["gaia/flora_bush_berry", "gaia/fauna_chicken", "gaia/fauna_chicken"]) { let currentAngle = randFloat(0, TWO_PI); // Stone and chicken let dAngle = TWO_PI * 2 / 9; let angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4); let dist = 12; let x = point.x + dist * Math.cos(angle); let y = point.y + dist * Math.sin(angle); placeMine({ "x": x, "y": y }, "gaia/geology_stonemine_temperate_quarry"); currentAngle += dAngle; // Wood let quantity = 80; dAngle = TWO_PI / quantity / 3; for (let i = 0; i < quantity; ++i) { angle = currentAngle + randFloat(0, dAngle); dist = randFloat(10, 15); let objectList = groveEntities; if (i % 2 == 0) objectList = groveActors; x = point.x + dist * Math.cos(angle); y = point.y + dist * Math.sin(angle); placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * PI)); createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter("temp_grass_plants"), paintClass(clGrove)]); currentAngle += dAngle; } // Metal and chicken dAngle = TWO_PI * 2 / 9; angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4); dist = 13; x = point.x + dist * Math.cos(angle); y = point.y + dist * Math.sin(angle); placeMine({ "x": x, "y": y }, "gaia/geology_metal_temperate_slabs"); currentAngle += dAngle; // Berries quantity = 15; dAngle = TWO_PI / quantity * 2 / 9; for (let i = 0; i < quantity; ++i) { angle = currentAngle + randFloat(0, dAngle); dist = randFloat(10, 15); x = point.x + dist * Math.cos(angle); y = point.y + dist * Math.sin(angle); placeObject(x, y, pickRandom(foodEntities), 0, randFloat(0, 2 * PI)); currentAngle += dAngle; } } log("Functions loaded after " + ((Date.now() - genStartTime) / 1000) + "s"); /** * Environment settings */ setBiome("alpine"); g_Environment.Fog.FogColor = { "r": 0.8, "g": 0.8, "b": 0.8, "a": 0.01 }; g_Environment.Water.WaterBody.Colour = { "r" : 0.3, "g" : 0.05, "b" : 0.1, "a" : 0.1 }; g_Environment.Water.WaterBody.Murkiness = 0.4; /** * Base terrain shape generation and settings */ // Height range by map size let heightScale = (g_Map.size + 256) / 768 / 4; let heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale }; // Water coverage let averageWaterCoverage = 1/5; // NOTE: Since terrain generation is quite unpredictable actual water coverage might vary much with the same value let waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); // Water height in environment and the engine let waterHeightAdjusted = waterHeight + MIN_HEIGHT; // Water height in RMGEN setWaterHeight(waterHeight); // Generate base terrain shape let medH = (heightRange.min + heightRange.max) / 2; let initialHeightmap = [[medH, medH], [medH, medH]]; setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8); // Apply simple erosion for (let i = 0; i < 5; ++i) splashErodeMap(0.1); // Final rescale rescaleHeightmap(heightRange.min, heightRange.max); RMS.SetProgress(25); /** * Prepare terrain texture placement */ let 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 let playerHeight = (heighLimits[4] + heighLimits[5]) / 2; // Average player height // Texture and actor presets let myBiome = []; myBiome.push({ // 0 Deep water "texture": ["shoreline_stoney_a"], "actor": [["gaia/fauna_fish", "actor|geology/stone_granite_boulder.xml"], 0.02], "textureHS": ["alpine_mountainside"], "actorHS": [["gaia/fauna_fish"], 0.1] }); myBiome.push({ // 1 Medium Water "texture": ["shoreline_stoney_a", "alpine_shore_rocks"], "actor": [["actor|geology/stone_granite_boulder.xml", "actor|geology/stone_granite_med.xml"], 0.03], "textureHS": ["alpine_mountainside"], "actorHS": [["actor|geology/stone_granite_boulder.xml", "actor|geology/stone_granite_med.xml"], 0.0] }); myBiome.push({ // 2 Shallow water "texture": ["alpine_shore_rocks"], "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], "textureHS": ["alpine_mountainside"], "actorHS": [["actor|props/flora/reeds_pond_dry.xml", "actor|geology/stone_granite_med.xml"], 0.1] }); myBiome.push({ // 3 Shore "texture": ["alpine_shore_rocks_grass_50", "alpine_grass_rocky"], "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], "textureHS": ["alpine_mountainside"], "actorHS": [["actor|props/flora/grass_soft_tuft_a.xml"], 0.1] }); myBiome.push({ // 4 Low ground "texture": ["alpine_dirt_grass_50", "alpine_grass_rocky"], "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], "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_soft_tuft_a.xml"], 0.1] }); myBiome.push({ // 5 Player and path height "texture": ["new_alpine_grass_c", "new_alpine_grass_b", "new_alpine_grass_d"], "actor": [["actor|geology/stone_granite_small.xml", "actor|props/flora/grass_soft_small.xml", "actor|props/flora/grass_medit_flowering_tall.xml"], 0.2], "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_small.xml", "actor|props/flora/grass_soft_small.xml"], 0.1] }); myBiome.push({ // 6 High ground "texture": ["new_alpine_grass_a", "alpine_grass_rocky"], "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], "textureHS": ["alpine_grass_rocky"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|props/flora/grass_tufts_a.xml"], 0.1] }); myBiome.push({ // 7 Lower forest border "texture": ["new_alpine_grass_mossy", "alpine_grass_rocky"], "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], "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|props/flora/grass_tufts_a.xml", "actor|geology/highland2_moss.xml"], 0.1] }); myBiome.push({ // 8 Forest "texture": ["alpine_forrestfloor"], "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], "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|geology/highland2_moss.xml", "actor|geology/stone_granite_med.xml"], 0.1] }); myBiome.push({ // 9 Upper forest border "texture": ["alpine_forrestfloor_snow", "new_alpine_grass_dirt_a"], "actor": [["gaia/flora_tree_pine", "actor|geology/snow1.xml"], 0.3], "textureHS": ["alpine_cliff_b"], "actorHS": [["actor|geology/stone_granite_med.xml", "actor|geology/snow1.xml"], 0.1] }); myBiome.push({ // 10 Hilltop "texture": ["alpine_cliff_a", "alpine_cliff_snow"], "actor": [["actor|geology/highland1.xml"], 0.05], "textureHS": ["alpine_cliff_c"], "actorHS": [["actor|geology/highland1.xml"], 0.0] }); log("Terrain shape generation and texture presets after " + ((Date.now() - genStartTime) / 1000) + "s"); /** * Get start locations */ let startLocations = getStartLocationsByHeightmap({ "min": heighLimits[4], "max": heighLimits[5] }, 1000, 30); // Sort start locations to form a "ring" let startLocationOrder = getOrderOfPointsForShortestClosePath(startLocations); let newStartLocations = []; for (let i = 0; i < startLocations.length; ++i) newStartLocations.push(startLocations[startLocationOrder[i]]); startLocations = newStartLocations; // Sort players by team let playerIDs = []; let teams = []; for (let i = 0; i < g_MapSettings.PlayerData.length - 1; ++i) { playerIDs.push(i+1); let t = g_MapSettings.PlayerData[i + 1].Team; if (teams.indexOf(t) == -1 && t !== undefined) teams.push(t); } playerIDs = sortPlayers(playerIDs); // Minimize maximum distance between players within a team if (teams.length) { let minDistance = Infinity; let bestShift; for (let s = 0; s < playerIDs.length; ++s) { let maxTeamDist = 0; for (let pi = 0; pi < playerIDs.length - 1; ++pi) { let p1 = playerIDs[(pi + s) % playerIDs.length] - 1; let t1 = getPlayerTeam(p1); if (teams.indexOf(t1) === -1) continue; for (let pj = pi + 1; pj < playerIDs.length; ++pj) { let p2 = playerIDs[(pj + s) % playerIDs.length] - 1; let t2 = getPlayerTeam(p2); if (t2 != t1) continue; - let l1 = startLocations[pi]; - let l2 = startLocations[pj]; - let dist = getDistance(l1.x, l1.y, l2.x, l2.y); - if (dist > maxTeamDist) - maxTeamDist = dist; + maxTeamDist = Math.max( + maxTeamDist, + Math.euclidDistance2D( + startLocations[pi].x, startLocations[pi].y, + startLocations[pj].x, startLocations[pj].y)); } } if (maxTeamDist < minDistance) { minDistance = maxTeamDist; bestShift = s; } } if (bestShift) { let newPlayerIDs = []; for (let i = 0; i < playerIDs.length; ++i) newPlayerIDs.push(playerIDs[(i + bestShift) % playerIDs.length]); playerIDs = newPlayerIDs; } } log("Start location chosen after " + ((Date.now() - genStartTime) / 1000) + "s"); RMS.SetProgress(30); /** * Smooth Start Locations before height region calculation */ for (let p = 0; p < playerIDs.length; ++p) rectangularSmoothToHeight(startLocations[p], 35, 35, playerHeight, 0.7); /** * Add paths */ let tchm = getTileCenteredHeightmap(); // Calculate tileCenteredHeightMap (This has nothing to to with TILE_CENTERED_HEIGHT_MAP which should be false) let pathPoints = []; let clPath = createTileClass(); for (let i = 0; i < startLocations.length; ++i) { let start = startLocations[i]; let target = startLocations[(i + 1) % startLocations.length]; pathPoints = pathPoints.concat(placeRandomPathToHeight(start, target, playerHeight, clPath)); } log("Paths placed after " + ((Date.now() - genStartTime) / 1000) + "s"); RMS.SetProgress(45); /** * Get resource spots after players start locations calculation */ let avoidPoints = clone(startLocations); for (let i = 0; i < avoidPoints.length; ++i) avoidPoints[i].dist = 30; let resourceSpots = getPointsByHeight({ "min": (heighLimits[3] + heighLimits[4]) / 2, "max": (heighLimits[5] + heighLimits[6]) / 2 }, avoidPoints, clPath); log("Resource spots chosen after " + ((Date.now() - genStartTime) / 1000) + "s"); RMS.SetProgress(55); /** * Divide tiles in areas by height and avoid paths */ let areas = []; for (let h = 0; h < heighLimits.length; ++h) areas.push([]); for (let x = 0; x < tchm.length; ++x) { for (let y = 0; y < tchm[0].length; ++y) { if (g_Map.tileClasses[clPath].inclusionCount[x][y] > 0) // Avoid paths continue; let minHeight = heightRange.min; for (let h = 0; h < heighLimits.length; ++h) { if (tchm[x][y] >= minHeight && tchm[x][y] <= heighLimits[h]) { areas[h].push({ "x": x, "y": y }); break; } else minHeight = heighLimits[h]; } } } /** * Get max slope of each area */ let slopeMap = getSlopeMap(); // Calculate slope map let minSlope = []; let maxSlope = []; for (let h = 0; h < heighLimits.length; ++h) { minSlope[h] = Infinity; maxSlope[h] = 0; for (let t = 0; t < areas[h].length; ++t) { let x = areas[h][t].x; let y = areas[h][t].y; let slope = slopeMap[x][y]; if (slope > maxSlope[h]) maxSlope[h] = slope; if (slope < minSlope[h]) minSlope[h] = slope; } } /** * Paint areas by height and slope */ for (let h = 0; h < heighLimits.length; ++h) { for (let t = 0; t < areas[h].length; ++t) { let x = areas[h][t].x; let y = areas[h][t].y; let actor = undefined; let texture = pickRandom(myBiome[h].texture); if (slopeMap[x][y] < 0.4 * (minSlope[h] + maxSlope[h])) { if (randBool(myBiome[h].actor[1])) actor = pickRandom(myBiome[h].actor[0]); } else { texture = pickRandom(myBiome[h].textureHS); if (randBool(myBiome[h].actorHS[1])) actor = pickRandom(myBiome[h].actorHS[0]); } g_Map.texture[x][y] = g_Map.getTextureID(texture); if (actor) placeObject(randFloat(x, x + 1), randFloat(y, y + 1), actor, 0, randFloat(0, 2 * PI)); } } log("Terrain texture placement finished after " + ((Date.now() - genStartTime) / 1000) + "s"); RMS.SetProgress(80); /** * Add start locations and resource spots after terrain texture and path painting */ for (let p = 0; p < playerIDs.length; ++p) { let point = startLocations[p]; placeCivDefaultEntities(point.x, point.y, playerIDs[p], { "iberWall": true }); placeStartLocationResources(startLocations[p]); } for (let i = 0; i < resourceSpots.length; ++i) { let choice = i % 5; if (choice == 0) placeMine(resourceSpots[i], "gaia/geology_stonemine_temperate_formation"); if (choice == 1) placeMine(resourceSpots[i], "gaia/geology_metal_temperate_slabs"); if (choice == 2) placeCustomFortress(resourceSpots[i].x, resourceSpots[i].y, pickRandom(fences), "other", 0, randFloat(0, 2 * PI)); if (choice == 3) placeGrove(resourceSpots[i]); if (choice == 4) placeCamp(resourceSpots[i]); } /** * Stop Timer */ log("Map generation finished after " + ((Date.now() - genStartTime) / 1000) + "s"); /** * Export map data */ ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/deep_forest.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/deep_forest.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/deep_forest.js (revision 20331) @@ -1,248 +1,249 @@ RMS.LoadLibrary("rmgen"); InitMap(); var clPlayer = createTileClass(); var clPath = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clBaseResource = createTileClass(); var templateStone = "gaia/geology_stone_temperate"; var templateStoneMine = "gaia/geology_stonemine_temperate_quarry"; var templateMetalMine = "gaia/geology_metal_temperate_slabs"; var startingResourcees = ["gaia/flora_tree_oak_large", "gaia/flora_bush_temperate", templateStoneMine, "gaia/flora_bush_grapes", "gaia/flora_tree_apple", "gaia/flora_bush_berry", templateMetalMine, "gaia/flora_bush_badlands"]; var terrainWood = ['temp_grass_mossy|gaia/flora_tree_oak', 'temp_forestfloor_pine|gaia/flora_tree_pine', 'temp_mud_plants|gaia/flora_tree_dead', 'temp_plants_bog|gaia/flora_tree_oak_large', "temp_dirt_gravel_plants|gaia/flora_tree_aleppo_pine", 'temp_forestfloor_autumn|gaia/flora_tree_carob']; //'temp_forestfloor_autumn|gaia/flora_tree_fig' var terrainWoodBorder = ['temp_grass_plants|gaia/flora_tree_euro_beech', 'temp_grass_mossy|gaia/flora_tree_poplar', 'temp_grass_mossy|gaia/flora_tree_poplar_lombardy', 'temp_grass_long|gaia/flora_bush_temperate', 'temp_mud_plants|gaia/flora_bush_temperate', 'temp_mud_plants|gaia/flora_bush_badlands', 'temp_grass_long|gaia/flora_tree_apple', 'temp_grass_clovers|gaia/flora_bush_berry', 'temp_grass_clovers_2|gaia/flora_bush_grapes', 'temp_grass_plants|gaia/fauna_deer', "temp_grass_long_b|gaia/fauna_rabbit", "temp_grass_plants"]; var terrainBase = ['temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_dirt_gravel', 'temp_grass_b', 'temp_grass_b|gaia/fauna_pig', 'temp_dirt_gravel|gaia/fauna_chicken']; var terrainBaseBorder = ["temp_grass_b", "temp_grass_b", "temp_grass", "temp_grass_c", "temp_grass_mossy"]; var terrainBaseCenter = ['temp_dirt_gravel', 'temp_dirt_gravel', 'temp_grass_b']; var terrainPath = ['temp_road', "temp_road_overgrown", 'temp_grass_b']; var terrainHill = ["temp_highlands", "temp_highlands", "temp_highlands", "temp_dirt_gravel_b", "temp_cliff_a"]; var terrainHillBorder = ["temp_highlands", "temp_highlands", "temp_highlands", "temp_dirt_gravel_b", "temp_dirt_gravel_plants", "temp_highlands", "temp_highlands", "temp_highlands", "temp_dirt_gravel_b", "temp_dirt_gravel_plants", "temp_highlands", "temp_highlands", "temp_highlands", "temp_cliff_b", "temp_dirt_gravel_plants", "temp_highlands", "temp_highlands", "temp_highlands", "temp_cliff_b", "temp_dirt_gravel_plants", "temp_highlands|gaia/fauna_goat"]; var mapSize = getMapSize(); var mapRadius = mapSize/2; var playableMapRadius = mapRadius - 5; var mapCenterX = mapRadius; var mapCenterZ = mapRadius; var numPlayers = getNumPlayers(); var baseRadius = 20; var minPlayerRadius = min(mapRadius-1.5*baseRadius, 5*mapRadius/8); var maxPlayerRadius = min(mapRadius-baseRadius, 3*mapRadius/4); var playerStartLocX = []; var playerStartLocZ = []; var playerAngle = []; var playerAngleStart = randFloat(0, 2*PI); var playerAngleAddAvrg = 2*PI / numPlayers; var playerAngleMaxOff = playerAngleAddAvrg/4; // Setup eyecandy var templateEC = "other/unfinished_greek_temple"; var radiusEC = max(mapRadius/8, baseRadius/2); // Setup paths var pathSucsessRadius = baseRadius/2; var pathAngleOff = PI/2; var pathWidth = 5; // This is not really the path's sickness 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 RMS.SetProgress(2); // Place bases 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; placeCivDefaultEntities(x, z, i+1); // Place base texture var placer = new ClumpPlacer(2*baseRadius*baseRadius, 2/3, 1/8, 10, x, z); var painter = [new LayeredPainter([terrainBaseBorder, terrainBase, terrainBaseCenter], [baseRadius/4, baseRadius/4]), paintClass(clPlayer)]; createArea(placer, painter); // Place starting resources var distToSL = 10; var resStartAngle = playerAngle[i] + PI; var resAddAngle = 2*PI / startingResourcees.length; for (var rIndex = 0; rIndex < startingResourcees.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, startingResourcees[rIndex], 0, randFloat(0, 2*PI)); addToClass(round(placeX), round(placeZ), clBaseResource); } } RMS.SetProgress(10); // Place paths var doublePaths = true; if (numPlayers > 4) doublePaths = false; 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 ElevationPainter(randFloat(-1, 0)), paintClass(clPath)]; createArea(placer, painter, avoidClasses(clHill, 0, 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) + if (Math.euclidDistance2D(x, z, targetX, targetZ) < pathSucsessRadius) targetReached = true; tries++; } } } RMS.SetProgress(50); // Place expansion resources for (var i=0; i < numPlayers; i++) { for (var rIndex = 0; rIndex < resourcePerPlayer.length; rIndex++) { if (numPlayers > 1) var angleDist = (playerAngle[(i+1)%numPlayers] - playerAngle[i] + 2*PI)%(2*PI); else var angleDist = 2*PI; var placeX = round(mapCenterX + resourceRadius*cos(playerAngle[i] + (rIndex+1)*angleDist/(resourcePerPlayer.length+1))); var placeZ = round(mapCenterX + resourceRadius*sin(playerAngle[i] + (rIndex+1)*angleDist/(resourcePerPlayer.length+1))); placeObject(placeX, placeZ, resourcePerPlayer[rIndex], 0, randFloat(0, 2*PI)); var placer = new ClumpPlacer(40, 1/2, 1/8, 1, placeX, placeZ); var painter = [new LayeredPainter([terrainHillBorder, terrainHill], [1]), new ElevationPainter(randFloat(1, 2)), paintClass(clHill)]; createArea(placer, painter); } } RMS.SetProgress(60); // Place eyecandy placeObject(mapCenterX, mapCenterZ, templateEC, 0, randFloat(0, 2*PI)); var placer = new ClumpPlacer(radiusEC*radiusEC, 1/2, 1/8, 1, mapCenterX, mapCenterZ); var painter = [new LayeredPainter([terrainHillBorder, terrainHill], [radiusEC/4]), new ElevationPainter(randFloat(1, 2)), paintClass(clHill)]; createArea(placer, painter); // Woods and general hight map for (var x = 0; x < mapSize; x++) { for (var z = 0;z < mapSize;z++) { - 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 + // The 0.5 is a correction for the entities placed on the center of tiles + var radius = Math.euclidDistance2D(x + 0.5, z + 0.5, mapCenterX, mapCenterZ); var minDistToSL = mapSize; for (var i=0; i < numPlayers; i++) - minDistToSL = min(minDistToSL, getDistance(playerStartLocX[i], playerStartLocZ[i], x, z)); + minDistToSL = Math.min(minDistToSL, Math.euclidDistance2D(x, z, playerStartLocX[i], playerStartLocZ[i])); // Woods tile based var tDensFactSL = max(min((minDistToSL - baseRadius) / baseRadius, 1), 0); var tDensFactRad = abs((resourceRadius - radius) / resourceRadius); var tDensFactEC = max(min((radius - radiusEC) / radiusEC, 1), 0); var tDensActual = maxTreeDensity * tDensFactSL * tDensFactRad * tDensFactEC; if (randBool(tDensActual) && radius < playableMapRadius) { if (tDensActual < randFloat(0, bushChance * maxTreeDensity)) { var placer = new ClumpPlacer(1, 1.0, 1.0, 1, x, z); var painter = [new TerrainPainter(terrainWoodBorder), new ElevationPainter(randFloat(0, 1)), paintClass(clForest)]; createArea(placer, painter, avoidClasses(clPath, 1, clHill, 0)); } else { var placer = new ClumpPlacer(1, 1.0, 1.0, 1, x, z); var painter = [new TerrainPainter(terrainWood), new ElevationPainter(randFloat(0, 1)), paintClass(clForest)]; createArea(placer, painter, avoidClasses(clPath, 2, clHill, 1)); } } // General hight map var hVarMiddleHill = mapSize/64 * (1+cos(3*PI/2 * radius/mapRadius)); var hVarHills = 5*(1+sin(x/10)*sin(z/10)); setHeight(x, z, getHeight(x, z) + hVarMiddleHill + hVarHills + 1); } } RMS.SetProgress(95); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/gear.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/gear.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/gear.js (revision 20331) @@ -1,381 +1,381 @@ RMS.LoadLibrary("rmgen"); RMS.LoadLibrary("rmbiome"); TILE_CENTERED_HEIGHT_MAP = true; setSelectedBiome(); const tMainTerrain = g_Terrains.mainTerrain; const tForestFloor1 = g_Terrains.forestFloor1; const tForestFloor2 = g_Terrains.forestFloor2; const tCliff = g_Terrains.cliff; const tTier1Terrain = g_Terrains.tier1Terrain; const tTier2Terrain = g_Terrains.tier2Terrain; const tTier3Terrain = g_Terrains.tier3Terrain; const tHill = g_Terrains.mainTerrain; const tRoad = g_Terrains.road; const tRoadWild = g_Terrains.roadWild; const tTier4Terrain = g_Terrains.tier4Terrain; const tShore = g_Terrains.shore; const tWater = g_Terrains.water; 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 oFruitBush = g_Gaia.fruitBush; const oMainHuntableAnimal = g_Gaia.mainHuntableAnimal; const oFish = g_Gaia.fish; const oSecondaryHuntableAnimal = g_Gaia.secondaryHuntableAnimal; const oStoneLarge = g_Gaia.stoneLarge; const oStoneSmall = g_Gaia.stoneSmall; const oMetalLarge = g_Gaia.metalLarge; 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 pForest1 = [tForestFloor2 + TERRAIN_SEPARATOR + oTree1, tForestFloor2 + TERRAIN_SEPARATOR + oTree2, tForestFloor2]; const pForest2 = [tForestFloor1 + TERRAIN_SEPARATOR + oTree4, tForestFloor1 + TERRAIN_SEPARATOR + oTree5, tForestFloor1]; InitMap(); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); const mapArea = mapSize*mapSize; var clPlayer = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clWater = createTileClass(); var clDirt = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); initTerrain(tMainTerrain); var [playerIDs, playerX, playerZ, playerAngle, startAngle] = radialPlayerPlacement(); for (var i = 0; i < numPlayers; i++) { var id = playerIDs[i]; log("Creating base for player " + id + "..."); var radius = scaleByMapSize(15,25); var cliffRadius = 2; var elevation = 20; // get the x and z in tiles var fx = fractionToTiles(playerX[i]); var fz = fractionToTiles(playerZ[i]); var ix = round(fx); var iz = round(fz); addCivicCenterAreaToClass(ix, iz, clPlayer); // create the city patch var cityRadius = radius/3; placer = new ClumpPlacer(PI*cityRadius*cityRadius, 0.6, 0.3, 10, ix, iz); var painter = new LayeredPainter([tRoadWild, tRoad], [1]); createArea(placer, painter, null); placeCivDefaultEntities(fx, fz, id, { 'iberWall': 'towers' }); placeDefaultChicken(fx, fz, clBaseResource); // create berry bushes var bbAngle = randFloat(0, TWO_PI); var bbDist = 12; var bbX = round(fx + bbDist * cos(bbAngle)); var bbZ = round(fz + bbDist * sin(bbAngle)); var group = new SimpleGroup( [new SimpleObject(oFruitBush, 5,5, 0,3)], true, clBaseResource, bbX, bbZ ); createObjectGroup(group, 0); // create metal mine 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(oMetalLarge, 1,1, 0,0)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); // create stone mines mAngle += randFloat(PI/8, PI/4); mX = round(fx + mDist * cos(mAngle)); mZ = round(fz + mDist * sin(mAngle)); group = new SimpleGroup( [new SimpleObject(oStoneLarge, 1,1, 0,2)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); // create starting trees var num = 2; 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(oTree1, num, num, 0,3)], false, clBaseResource, tX, tZ ); createObjectGroup(group, 0, avoidClasses(clBaseResource,2)); placeDefaultDecoratives(fx, fz, aGrassShort, clBaseResource, radius); } RMS.SetProgress(20); var split = 1; if (mapSize == 128 && numPlayers <= 2) split = 2; else if (mapSize == 192 && numPlayers <= 3) split = 2; else if (mapSize == 256) { if (numPlayers <= 3) split = 3; else if (numPlayers == 4) split = 2; } else if (mapSize == 320) { if (numPlayers <= 3) split = 3; else if (numPlayers == 4) split = 2; } else if (mapSize == 384) { if (numPlayers <= 3) split = 4; else if (numPlayers == 4) split = 3; else if (numPlayers == 5) split = 2; } else if (mapSize == 448) { if (numPlayers <= 2) split = 5; else if (numPlayers <= 4) split = 4; else if (numPlayers == 5) split = 3; else if (numPlayers == 6) split = 2; } log ("Creating big circular lake..."); var center = Math.round(fractionToTiles(0.5)); createArea( new ClumpPlacer(mapArea * 0.23, 1, 1, 10, center, center), new SmoothElevationPainter(ELEVATION_SET, -3, 4), null); for (let m = 0; m < numPlayers * split; ++m) { log("Creating rivers between players..."); let angle = startAngle + (m + 0.5) * 2 * Math.PI / (numPlayers * split); createArea( new PathPlacer( fractionToTiles(0.5 + 0.15 * Math.cos(angle)), fractionToTiles(0.5 + 0.15 * Math.sin(angle)), fractionToTiles(0.5 + 0.6 * Math.cos(angle)), fractionToTiles(0.5 + 0.6 * Math.sin(angle)), scaleByMapSize(14, 40), 0, 3 * scaleByMapSize(1, 3), 0.2, 0.05), new SmoothElevationPainter(ELEVATION_SET, -4, 4), avoidClasses(clPlayer, 5)); log("Create path from the island to the center..."); angle = startAngle + m * 2 * Math.PI / (numPlayers * split); createArea( new PathPlacer( fractionToTiles(0.5 + 0.05 * Math.cos(angle)), fractionToTiles(0.5 + 0.05 * Math.sin(angle)), fractionToTiles(0.5 + 0.49 * Math.cos(angle)), fractionToTiles(0.5 + 0.49 * Math.sin(angle)), scaleByMapSize(10, 40), 0, 3 * scaleByMapSize(1, 3), 0.2, 0.05), new SmoothElevationPainter(ELEVATION_SET, 3, 4), null); } log("Creating ring of land connecting players..."); createArea( new ClumpPlacer(mapArea * 0.15, 1, 1, 10, center, center), new SmoothElevationPainter(ELEVATION_SET, 4, 4), null); log("Creating ring of water separating the central hill from the ring..."); createArea( new ClumpPlacer(mapArea * 0.09, 1, 1, 10, center, center), new SmoothElevationPainter(ELEVATION_SET, -2, 3), null); log("Creating central island..."); createArea( - new ClumpPlacer(Math.pow(mapSize - 50, 2) * 0.09, 1, 1, 10, center, center), + new ClumpPlacer(Math.square(mapSize - 50) * 0.09, 1, 1, 10, center, center), new SmoothElevationPainter(ELEVATION_SET, 4, 3), null); log("Creating hill on the central island..."); createArea( new ClumpPlacer(Math.pow(scaleByMapSize(6, 18), 2) * 22, 1, 1, 10, center, center), new SmoothElevationPainter(ELEVATION_SET, 20, 8), null); paintTerrainBasedOnHeight(-6, 1, 1, tWater); paintTerrainBasedOnHeight(1, 2, 1, tShore); paintTerrainBasedOnHeight(2, 21, 1, tMainTerrain); paintTileClassBasedOnHeight(-6, 0.5, 1, clWater); for (var i = 0; i < numPlayers; i++) { fx = fractionToTiles(playerX[i]); fz = fractionToTiles(playerZ[i]); ix = round(fx); iz = round(fz); // create the city patch var cityRadius = radius/3; var placer = new ClumpPlacer(PI*cityRadius*cityRadius, 0.6, 0.3, 10, ix, iz); var painter = new LayeredPainter([tRoadWild, tRoad], [1]); createArea(placer, painter, null); } if (randBool()) createHills([tMainTerrain, tCliff, tHill], avoidClasses(clPlayer, 20, clHill, 15, clWater, 2), clHill, scaleByMapSize(1, 4) * numPlayers); else createMountains(tCliff, avoidClasses(clPlayer, 20, clHill, 15, clWater, 2), clHill, scaleByMapSize(1, 4) * numPlayers); createForests( [tMainTerrain, tForestFloor1, tForestFloor2, pForest1, pForest2], avoidClasses(clPlayer, 20, clForest, 17, clHill, 0, clWater, 2), clForest, 1, ...rBiomeTreeCount(1)); 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(clWater, 3, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12) ); log("Creating grass patches..."); createPatches( [scaleByMapSize(2, 4), scaleByMapSize(3, 7), scaleByMapSize(5, 15)], tTier4Terrain, avoidClasses(clWater, 3, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12) ); RMS.SetProgress(55); log("Creating stone mines..."); createMines( [ [new SimpleObject(oStoneSmall, 0,2, 0,4), new SimpleObject(oStoneLarge, 1,1, 0,4)], [new SimpleObject(oStoneSmall, 2,5, 1,3)] ], avoidClasses(clWater, 3, clForest, 1, clPlayer, 20, clRock, 10, clHill, 1) ); log("Creating metal mines..."); createMines( [ [new SimpleObject(oMetalLarge, 1,1, 0,4)] ], avoidClasses(clWater, 3, clForest, 1, clPlayer, 20, clMetal, 10, clRock, 5, clHill, 1), clMetal ); log("Creating fish..."); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(oFish, 1,1, 0,3)], true, clFood), 0, [stayClasses(clWater, 8), avoidClasses(clFood, 14)], scaleByMapSize(400, 2000), 100); RMS.SetProgress(65); 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(clWater, 0, clForest, 0, clPlayer, 0, clHill, 0) ); RMS.SetProgress(70); createFood ( [ [new SimpleObject(oMainHuntableAnimal, 5,7, 0,4)], [new SimpleObject(oSecondaryHuntableAnimal, 2,3, 0,2)] ], [ 3 * numPlayers, 3 * numPlayers ], avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clHill, 1, clFood, 20) ); createFood ( [ [new SimpleObject(oFruitBush, 5,7, 0,4)] ], [ 3 * numPlayers ], avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clHill, 1, clFood, 10) ); createStragglerTrees( [oTree1, oTree2, oTree4, oTree3], avoidClasses(clWater, 5, clForest, 7, clHill, 1, clPlayer, 12, clMetal, 6, clRock, 6)); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/heightmap/heightmap.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/heightmap/heightmap.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/heightmap/heightmap.js (revision 20331) @@ -1,532 +1,532 @@ /** * Heightmap manipulation functionality * * A heightmapt is an array of width arrays of height floats * Width and height is normally mapSize+1 (Number of vertices is one bigger than number of tiles in each direction) * The default heightmap is g_Map.height (See the Map object) * * @warning - Ambiguous naming and potential confusion: * To use this library use TILE_CENTERED_HEIGHT_MAP = false (default) * Otherwise TILE_CENTERED_HEIGHT_MAP has nothing to do with any tile centered map in this library * @todo - TILE_CENTERED_HEIGHT_MAP should be removed and g_Map.height should never be tile centered */ /** * Get the height range of a heightmap * @param {array} [heightmap=g_Map.height] - The reliefmap the minimum and maximum height should be determined for * @return {object} [height] - Height range with 2 floats in properties "min" and "max" */ function getMinAndMaxHeight(heightmap = g_Map.height) { let height = {}; height.min = Infinity; height.max = - Infinity; for (let x = 0; x < heightmap.length; ++x) { for (let y = 0; y < heightmap[x].length; ++y) { if (heightmap[x][y] < height.min) height.min = heightmap[x][y]; else if (heightmap[x][y] > height.max) height.max = heightmap[x][y]; } } return height; } /** * Rescales a heightmap so its minimum and maximum height is as the arguments told preserving it's global shape * @param {float} [minHeight=MIN_HEIGHT] - Minimum height that should be used for the resulting heightmap * @param {float} [maxHeight=MAX_HEIGHT] - Maximum height that should be used for the resulting heightmap * @param {array} [heightmap=g_Map.height] - A reliefmap * @todo Add preserveCostline to leave a certain height untoucht and scale below and above that seperately */ function rescaleHeightmap(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, heightmap = g_Map.height) { let oldHeightRange = getMinAndMaxHeight(heightmap); let max_x = heightmap.length; let max_y = heightmap[0].length; for (let x = 0; x < max_x; ++x) for (let y = 0; y < max_y; ++y) heightmap[x][y] = minHeight + (heightmap[x][y] - oldHeightRange.min) / (oldHeightRange.max - oldHeightRange.min) * (maxHeight - minHeight); } /** * Get start location with the largest minimum distance between players * @param {array} [heightRange] - The height range start locations are allowed * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared * @param {float} [minDistToBorder=20] - How far start locations have to be away from the map border * @param {integer} [numberOfPlayers=g_MapSettings.PlayerData.length] - How many start locations should be placed * @param {array} [heightmap=g_Map.height] - The reliefmap for the start locations to be placed on * @param {boolean} [isCircular=g_MapSettings.CircularMap] - If the map is circular or rectangular * @return {array} [finalStartLoc] - Array of 2D points in the format { "x": float, "y": float} */ function getStartLocationsByHeightmap(heightRange, maxTries = 1000, minDistToBorder = 20, numberOfPlayers = g_MapSettings.PlayerData.length - 1, heightmap = g_Map.height, isCircular = g_MapSettings.CircularMap) { let validStartLoc = []; let r = 0.5 * (heightmap.length - 1); // Map center x/y as well as radius for (let x = minDistToBorder; x < heightmap.length - minDistToBorder; ++x) for (let y = minDistToBorder; y < heightmap[0].length - minDistToBorder; ++y) if (heightmap[x][y] > heightRange.min && heightmap[x][y] < heightRange.max) // Is in height range - if (!isCircular || r - getDistance(x, y, r, r) >= minDistToBorder) // Is far enough away from map border + if (!isCircular || r - Math.euclidDistance2D(x, y, r, r) >= minDistToBorder) // Is far enough away from map border validStartLoc.push({ "x": x, "y": y }); let maxMinDist = 0; let finalStartLoc; for (let tries = 0; tries < maxTries; ++tries) { let startLoc = []; let minDist = Infinity; for (let p = 0; p < numberOfPlayers; ++p) startLoc.push(pickRandom(validStartLoc)); for (let p1 = 0; p1 < numberOfPlayers - 1; ++p1) { for (let p2 = p1 + 1; p2 < numberOfPlayers; ++p2) { - let dist = getDistance(startLoc[p1].x, startLoc[p1].y, startLoc[p2].x, startLoc[p2].y); + let dist = Math.euclidDistance2D(startLoc[p1].x, startLoc[p1].y, startLoc[p2].x, startLoc[p2].y); if (dist < minDist) minDist = dist; } } if (minDist > maxMinDist) { maxMinDist = minDist; finalStartLoc = startLoc; } } return finalStartLoc; } /** * Meant to place e.g. resource spots within a height range * * @param {array} heightRange - The height range in which to place the entities (An associative array with keys "min" and "max" each containing a float) * @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 * @param {number} minDistance - How many tile widths the entities to place have to be away from each other, start locations and the map border * @param {array} entityList - Entity/actor strings to be placed with placeObject() * @param {array} [heightmap=g_Map.height] - The reliefmap the entities should be distributed on * @param {number} [playerID=0] - Index of the player the entities should be placed for. Gaia is 0. * @param {number} [maxTries=1000] - How often random player distributions are rolled to be compared * @param {boolean} [isCircular=g_MapSettings.CircularMap] - If the map is circular or rectangular * @return {array} [placements] Array of points where entities were placed */ function distributeEntitiesByHeight(heightRange, avoidPoints, minDistance, entityList, playerID = 0, maxTries = 1000, heightmap = g_Map.height, isCircular = g_MapSettings.CircularMap) { let validPoints = []; let r = 0.5 * (heightmap.length - 1); // Map center x/y as well as radius for (let x = minDistance; x < heightmap.length - minDistance; ++x) { for (let y = minDistance; y < heightmap[0].length - minDistance; ++y) { if (heightmap[x][y] < heightRange.min || heightmap[x][y] > heightRange.max) continue; // Out of height range let checkpoint = { "x" : x + 0.5, "y" : y + 0.5 }; - if (isCircular && r - getDistance(checkpoint.x, checkpoint.y, r, r) < minDistance) + if (isCircular && r - Math.euclidDistance2D(checkpoint.x, checkpoint.y, r, r) < minDistance) continue; // Too close to map border // Avoid points by minDistance, else add to validPoints - if (avoidPoints.every(ap => getDistance(checkpoint.x, checkpoint.y, ap.x, ap.y) > minDistance)) + if (avoidPoints.every(ap => Math.euclidDistance2D(checkpoint.x, checkpoint.y, ap.x, ap.y) > minDistance)) validPoints.push(checkpoint); } } let placements = []; if (!validPoints.length) { log("No placement points found for the given arguments (entityList=" + uneval(entityList) + "):\n" + new Error().stack); return placements; } for (let tries = 0; tries < maxTries; ++tries) { let checkPointIndex = randIntExclusive(0, validPoints.length); let checkPoint = validPoints[checkPointIndex]; - if (placements.every(p => getDistance(p.x, p.y, checkPoint.x, checkPoint.y) > minDistance)) + if (placements.every(p => Math.euclidDistance2D(p.x, p.y, checkPoint.x, checkPoint.y) > minDistance)) { placeObject(checkPoint.x, checkPoint.y, pickRandom(entityList), playerID, randFloat(0, 2*PI)); placements.push(checkPoint); } validPoints.splice(checkPointIndex); if (!validPoints.length) break; // No more valid points left } if (!placements.length) log("Nothing was placed:\n" + new Error().stack); return placements; } /** * Sets a given heightmap to entirely random values within a given range * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled * @param {array} [heightmap=g_Map.height] - The reliefmap that should be randomized */ function setRandomHeightmap(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, heightmap = g_Map.height) { for (let x = 0; x < heightmap.length; ++x) for (let y = 0; y < heightmap[0].length; ++y) heightmap[x][y] = randFloat(minHeight, maxHeight); } /** * Sets the heightmap to a relatively realistic shape * The function doubles the size of the initial heightmap (if given, else a random 2x2 one) until it's big enough, then the extend is cut off * @note min/maxHeight will not necessarily be present in the heightmap * @note On circular maps the edges (given by initialHeightmap) may not be in the playable map area * @note The impact of the initial heightmap depends on its size and target map size * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled * @param {array} [initialHeightmap] - Optional, Small (e.g. 3x3) heightmap describing the global shape of the map e.g. an island [[MIN_HEIGHT, MIN_HEIGHT, MIN_HEIGHT], [MIN_HEIGHT, MAX_HEIGHT, MIN_HEIGHT], [MIN_HEIGHT, MIN_HEIGHT, MIN_HEIGHT]] * @param {float} [smoothness=0.5] - Float between 0 (rough, more local structures) to 1 (smoother, only larger scale structures) * @param {array} [heightmap=g_Map.height] - The reliefmap that will be set by this function */ function setBaseTerrainDiamondSquare(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, initialHeightmap = undefined, smoothness = 0.5, heightmap = g_Map.height) { initialHeightmap = (initialHeightmap || [[randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)], [randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)]]); let heightRange = maxHeight - minHeight; if (heightRange <= 0) warn("setBaseTerrainDiamondSquare: heightRange <= 0"); let offset = heightRange / 2; // Double initialHeightmap width until target width is reached (diamond square method) let newHeightmap = []; while (initialHeightmap.length < heightmap.length) { newHeightmap = []; let oldWidth = initialHeightmap.length; // Square for (let x = 0; x < 2 * oldWidth - 1; ++x) { newHeightmap.push([]); for (let 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 (let x = 0; x < 2 * oldWidth - 1; ++x) { for (let y = 0; y < 2 * oldWidth - 1; ++y) { if (newHeightmap[x][y] !== undefined) continue; 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 = clone(newHeightmap); offset /= Math.pow(2, smoothness); } // Cut initialHeightmap to fit target width let shift = [floor((newHeightmap.length - heightmap.length) / 2), floor((newHeightmap[0].length - heightmap[0].length) / 2)]; for (let x = 0; x < heightmap.length; ++x) for (let y = 0; y < heightmap[0].length; ++y) heightmap[x][y] = newHeightmap[x + shift[0]][y + shift[1]]; } /** * Smoothens the entire map * @param {float} [strength=0.8] - How strong the smooth effect should be: 0 means no effect at all, 1 means quite strong, higher values might cause interferences, better apply it multiple times * @param {array} [heightmap=g_Map.height] - The heightmap to be smoothed * @param {array} [smoothMap=[[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]] - Array of offsets discribing the neighborhood tiles to smooth the height of a tile to */ function globalSmoothHeightmap(strength = 0.8, heightmap = g_Map.height, smoothMap = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]) { let referenceHeightmap = clone(heightmap); let max_x = heightmap.length; let max_y = heightmap[0].length; for (let x = 0; x < max_x; ++x) { for (let y = 0; y < max_y; ++y) { for (let i = 0; i < smoothMap.length; ++i) { let mapX = x + smoothMap[i][0]; let mapY = y + smoothMap[i][1]; if (mapX >= 0 && mapX < max_x && mapY >= 0 && mapY < max_y) heightmap[x][y] += strength / smoothMap.length * (referenceHeightmap[mapX][mapY] - referenceHeightmap[x][y]); } } } } /** * Pushes a rectangular area towards a given height smoothing it into the original terrain * @note The window function to determine the smooth is not exactly a gaussian to ensure smooth edges * @param {object} [center] - The x and y coordinates of the center point (rounded in this function) * @param {float} [dx] - Distance from the center in x direction the rectangle ends (half width, rounded in this function) * @param {float} [dy] - Distance from the center in y direction the rectangle ends (half depth, rounded in this function) * @param {float} [targetHeight] - Height the center of the rectangle will be pushed to * @param {float} [strength=1] - How strong the height is pushed: 0 means not at all, 1 means the center will be pushed to the target height * @param {array} [heightmap=g_Map.height] - The heightmap to be manipulated * @todo Make the window function an argument and maybe add some */ function rectangularSmoothToHeight(center, dx, dy, targetHeight, strength = 0.8, heightmap = g_Map.height) { let x = round(center.x); let y = round(center.y); dx = round(dx); dy = round(dy); let heightmapWin = []; for (let wx = 0; wx < 2 * dx + 1; ++wx) { heightmapWin.push([]); for (let wy = 0; wy < 2 * dy + 1; ++wy) { let actualX = x - dx + wx; let 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 (let wx = 0; wx < 2 * dx + 1; ++wx) { for (let wy = 0; wy < 2 * dy + 1; ++wy) { let actualX = x - dx + wx; let 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 let scaleX = 1 - (wx / dx - 1) * (wx / dx - 1); let scaleY = 1 - (wy / dy - 1) * (wy / dy - 1); heightmap[actualX][actualY] = heightmapWin[wx][wy] + strength * scaleX * scaleY * (targetHeight - heightmapWin[wx][wy]); } } } } /** * Meant to place e.g. resource spots within a height range * @param {array} [heightRange] - The height range in which to place the entities (An associative array with keys "min" and "max" each containing a float) * @param {array} [avoidPoints=[]] - An array of objects of the form { "x": int, "y": int, "dist": int }, points that will be avoided in the given dist e.g. start locations * @param {object} [avoidClass=undefined] - TileClass to be avoided * @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 * @param {array} [heightmap=g_Map.height] - The reliefmap the entities should be distributed on * @param {integer} [maxTries=2 * g_Map.size] - How often random player distributions are rolled to be compared (256 to 1024) * @param {boolean} [isCircular=g_MapSettings.CircularMap] - If the map is circular or rectangular */ function getPointsByHeight(heightRange, avoidPoints = [], avoidClass = undefined, minDistance = 20, maxTries = 2 * g_Map.size, heightmap = g_Map.height, isCircular = g_MapSettings.CircularMap) { let points = []; let placements = clone(avoidPoints); let validVertices = []; let r = 0.5 * (heightmap.length - 1); // Map center x/y as well as radius let avoidMap; if (avoidClass !== undefined) avoidMap = g_Map.tileClasses[avoidClass].inclusionCount; for (let x = minDistance; x < heightmap.length - minDistance; ++x) { for (let y = minDistance; y < heightmap[0].length - minDistance; ++y) { if (avoidClass !== undefined && // Avoid adjecting tiles in avoidClass (avoidMap[max(x - 1, 0)][y] > 0 || avoidMap[x][max(y - 1, 0)] > 0 || avoidMap[min(x + 1, avoidMap.length - 1)][y] > 0 || avoidMap[x][min(y + 1, avoidMap[0].length - 1)] > 0)) continue; if (heightmap[x][y] > heightRange.min && heightmap[x][y] < heightRange.max && // Has correct height - (!isCircular || r - getDistance(x, y, r, r) >= minDistance)) // Enough distance to map border + (!isCircular || r - Math.euclidDistance2D(x, y, r, r) >= minDistance)) // Enough distance to the map border validVertices.push({ "x": x, "y": y , "dist": minDistance}); } } for (let tries = 0; tries < maxTries; ++tries) { let point = pickRandom(validVertices); - if (placements.every(p => getDistance(p.x, p.y, point.x, point.y) > max(minDistance, p.dist))) + if (placements.every(p => Math.euclidDistance2D(p.x, p.y, point.x, point.y) > Math.max(minDistance, p.dist))) { points.push(point); placements.push(point); } if (tries != 0 && tries % 100 == 0) // Time Check log(points.length + " points found after " + tries + " tries after " + ((Date.now() - genStartTime) / 1000) + "s"); } return points; } /** * Returns an approximation of the heights of the tiles between the vertices, a tile centered heightmap * A tile centered heightmap is one smaller in width and height than an ordinary heightmap * It is meant to e.g. texture a map by height (x/y coordinates correspond to those of the terrain texture map) * Don't use this to override g_Map height (Potentially breaks the map)! * @param {array} [heightmap=g_Map.height] - A reliefmap the tile centered version should be build from */ function getTileCenteredHeightmap(heightmap = g_Map.height) { let max_x = heightmap.length - 1; let max_y = heightmap[0].length - 1; let tchm = []; for (let x = 0; x < max_x; ++x) { tchm[x] = new Float32Array(max_y); for (let y = 0; y < max_y; ++y) tchm[x][y] = 0.25 * (heightmap[x][y] + heightmap[x + 1][y] + heightmap[x][y + 1] + heightmap[x + 1][y + 1]); } return tchm; } /** * Returns a slope map (same form as the a heightmap with one less width and height) * Not normalized. Only returns the steepness (float), not the direction of incline. * The x and y coordinates of a tile in the terrain texture map correspond to those of the slope map * @param {array} [inclineMap=getInclineMap(g_Map.height)] - A map with the absolute inclination for each tile */ function getSlopeMap(inclineMap = getInclineMap(g_Map.height)) { let max_x = inclineMap.length; let slopeMap = []; for (let x = 0; x < max_x; ++x) { let max_y = inclineMap[x].length; slopeMap[x] = new Float32Array(max_y); for (let y = 0; y < max_y; ++y) - slopeMap[x][y] = Math.pow(inclineMap[x][y].x * inclineMap[x][y].x + inclineMap[x][y].y * inclineMap[x][y].y, 0.5); + slopeMap[x][y] = Math.euclidDistance2D(0, 0, inclineMap[x][y].x, inclineMap[x][y].y); } return slopeMap; } /** * Returns an inclination map corresponding to the tiles between the heightmaps vertices: * array of heightmap width-1 arrays of height-1 vectors (associative arrays) of the form: * { "x": x_slope, "y": y_slope ] so a 2D Vector pointing to the hightest incline (with the length the incline in the vectors direction) * The x and y coordinates of a tile in the terrain texture map correspond to those of the inclination map * @param {array} [heightmap=g_Map.height] - The reliefmap the inclination map is to be generated from */ function getInclineMap(heightmap) { heightmap = (heightmap || g_Map.height); let max_x = heightmap.length - 1; let max_y = heightmap[0].length - 1; let inclineMap = []; for (let x = 0; x < max_x; ++x) { inclineMap[x] = []; for (let y = 0; y < max_y; ++y) { let dx = heightmap[x + 1][y] - heightmap[x][y]; let dy = heightmap[x][y + 1] - heightmap[x][y]; let next_dx = heightmap[x + 1][y + 1] - heightmap[x][y + 1]; let next_dy = heightmap[x + 1][y + 1] - heightmap[x + 1][y]; inclineMap[x][y] = { "x" : 0.5 * (dx + next_dx), "y" : 0.5 * (dy + next_dy) }; } } return inclineMap; } function getGrad(wrapped = true, scalarField = g_Map.height) { let vectorField = []; let max_x = scalarField.length; let max_y = scalarField[0].length; if (!wrapped) { max_x -= 1; max_y -= 1; } for (let x = 0; x < max_x; ++x) { vectorField.push([]); for (let y = 0; y < max_y; ++y) { vectorField[x].push({ "x" : scalarField[(x + 1) % max_x][y] - scalarField[x][y], "y" : scalarField[x][(y + 1) % max_y] - scalarField[x][y] }); } } return vectorField; } function splashErodeMap(strength = 1, heightmap = g_Map.height) { let max_x = heightmap.length; let max_y = heightmap[0].length; let dHeight = getGrad(heightmap); for (let x = 0; x < max_x; ++x) { let next_x = (x + 1) % max_x; let prev_x = (x + max_x - 1) % max_x; for (let y = 0; y < max_y; ++y) { let next_y = (y + 1) % max_y; let prev_y = (y + max_y - 1) % max_y; let slopes = [- dHeight[x][y].x, - dHeight[x][y].y, dHeight[prev_x][y].x, dHeight[x][prev_y].y]; let sumSlopes = 0; for (let i = 0; i < slopes.length; ++i) if (slopes[i] > 0) sumSlopes += slopes[i]; let drain = []; for (let i = 0; i < slopes.length; ++i) { drain.push(0); if (slopes[i] > 0) drain[i] += min(strength * slopes[i] / sumSlopes, slopes[i]); } let sumDrain = 0; for (let i = 0; i < drain.length; ++i) sumDrain += drain[i]; // Apply changes to maps heightmap[x][y] -= sumDrain; heightmap[next_x][y] += drain[0]; heightmap[x][next_y] += drain[1]; heightmap[prev_x][y] += drain[2]; heightmap[x][prev_y] += drain[3]; } } } Index: ps/trunk/binaries/data/mods/public/maps/random/islands.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/islands.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/islands.js (revision 20331) @@ -1,498 +1,498 @@ RMS.LoadLibrary("rmgen"); RMS.LoadLibrary("rmbiome"); TILE_CENTERED_HEIGHT_MAP = true; setSelectedBiome(); const tMainTerrain = g_Terrains.mainTerrain; const tForestFloor1 = g_Terrains.forestFloor1; const tForestFloor2 = g_Terrains.forestFloor2; const tCliff = g_Terrains.cliff; const tTier1Terrain = g_Terrains.tier1Terrain; const tTier2Terrain = g_Terrains.tier2Terrain; const tTier3Terrain = g_Terrains.tier3Terrain; const tHill = g_Terrains.hill; const tRoad = g_Terrains.road; const tRoadWild = g_Terrains.roadWild; const tTier4Terrain = g_Terrains.tier4Terrain; const tShore = g_Terrains.shore; const tWater = g_Terrains.water; 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 oFruitBush = g_Gaia.fruitBush; const oMainHuntableAnimal = g_Gaia.mainHuntableAnimal; const oFish = g_Gaia.fish; const oSecondaryHuntableAnimal = g_Gaia.secondaryHuntableAnimal; const oStoneLarge = g_Gaia.stoneLarge; const oStoneSmall = g_Gaia.stoneSmall; const oMetalLarge = g_Gaia.metalLarge; const oWood = "gaia/special_treasure_wood"; 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 pForest1 = [tForestFloor2 + TERRAIN_SEPARATOR + oTree1, tForestFloor2 + TERRAIN_SEPARATOR + oTree2, tForestFloor2]; const pForest2 = [tForestFloor1 + TERRAIN_SEPARATOR + oTree4, tForestFloor1 + TERRAIN_SEPARATOR + oTree5, tForestFloor1]; InitMap(); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); var clPlayer = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clDirt = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var clLand = createTileClass(); var [playerIDs, playerX, playerZ, playerAngle] = radialPlayerPlacement(); for (var i = 0; i < numPlayers; i++) { var id = playerIDs[i]; log("Creating base for player " + id + "..."); var radius = scaleByMapSize(20,29); var shoreRadius = 6; var elevation = 3; var hillSize = PI * radius * radius; // get the x and z in tiles var fx = fractionToTiles(playerX[i]); var fz = fractionToTiles(playerZ[i]); var ix = round(fx); var iz = round(fz); // create the hill var placer = new ClumpPlacer(hillSize, 0.80, 0.1, 10, ix, iz); var terrainPainter = new LayeredPainter( [tMainTerrain , tMainTerrain, tMainTerrain], // terrains [1, shoreRadius] // widths ); var elevationPainter = new SmoothElevationPainter( ELEVATION_SET, // type elevation, // elevation shoreRadius // blend radius ); createArea(placer, [terrainPainter, elevationPainter, paintClass(clPlayer)], null); placeCivDefaultEntities(fx, fz, id, { 'iberWall': 'towers' }); placeDefaultChicken(fx, fz, clBaseResource); // create berry bushes var bbAngle = randFloat(0, TWO_PI); var bbDist = 12; var bbX = round(fx + bbDist * cos(bbAngle)); var bbZ = round(fz + bbDist * sin(bbAngle)); var group = new SimpleGroup( [new SimpleObject(oFruitBush, 5,5, 0,3)], true, clBaseResource, bbX, bbZ ); createObjectGroup(group, 0); // create woods var bbAngle = randFloat(0, TWO_PI); var bbDist = 13; var bbX = round(fx + bbDist * cos(bbAngle)); var bbZ = round(fz + bbDist * sin(bbAngle)); group = new SimpleGroup( [new SimpleObject(oWood, 14,14, 0,3)], true, clBaseResource, bbX, bbZ ); createObjectGroup(group, 0); // create metal mine 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(oMetalLarge, 1,1, 0,0)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); // create stone mines mAngle += randFloat(PI/8, PI/4); mX = round(fx + mDist * cos(mAngle)); mZ = round(fz + mDist * sin(mAngle)); group = new SimpleGroup( [new SimpleObject(oStoneLarge, 1,1, 0,2)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); var hillSize = PI * radius * radius; // create starting trees var num = 5; var tAngle = randFloat(-PI/3, 4*PI/3); var tDist = randFloat(12, 13); var tX = round(fx + tDist * cos(tAngle)); var tZ = round(fz + tDist * sin(tAngle)); group = new SimpleGroup( [new SimpleObject(oTree1, num, num, 0,3)], false, clBaseResource, tX, tZ ); createObjectGroup(group, 0, avoidClasses(clBaseResource,2)); placeDefaultDecoratives(fx, fz, aGrassShort, clBaseResource, radius); //create docks var dockLocation = getTIPIADBON([ix, iz], [mapSize / 2, mapSize / 2], [-3 , 2.6], 0.5, 3); if (dockLocation !== undefined) placeObject(dockLocation[0], dockLocation[1], "structures/" + getCivCode(id-1) + "_dock", id, playerAngle[i] + PI); } var landAreas = []; var playerConstraint = new AvoidTileClassConstraint(clPlayer, floor(scaleByMapSize(12,16))); var landConstraint = new AvoidTileClassConstraint(clLand, floor(scaleByMapSize(12,16))); for (var x = 0; x < mapSize; ++x) for (var z = 0; z < mapSize; ++z) if (playerConstraint.allows(x, z) && landConstraint.allows(x, z)) landAreas.push([x, z]); var chosenPoint; var landAreaLen; log("Creating big islands..."); for (let i = 0; i < scaleByMapSize(4, 14); ++i) { landAreaLen = landAreas.length; if (!landAreaLen) break; chosenPoint = pickRandom(landAreas); var newIsland = createAreas( new ChainPlacer( Math.floor(scaleByMapSize(4, 8)), Math.floor(scaleByMapSize(8, 14)), Math.floor(scaleByMapSize(25, 60)), 0.07, chosenPoint[0], chosenPoint[1], scaleByMapSize(30, 70)), [ new LayeredPainter([tMainTerrain, tMainTerrain], [2]), new SmoothElevationPainter(ELEVATION_SET, 3, 6), paintClass(clLand) ], avoidClasses(clLand, 3, clPlayer, 3), 1, 1); if (newIsland && newIsland.length) { var n = 0; for (var j = 0; j < landAreas.length; ++j) { let [x, z] = landAreas[j]; if (playerConstraint.allows(x, z) && landConstraint.allows(x, z)) landAreas[n++] = landAreas[j]; } landAreas.length = n; } } playerConstraint = new AvoidTileClassConstraint(clPlayer, floor(scaleByMapSize(9,12))); landConstraint = new AvoidTileClassConstraint(clLand, floor(scaleByMapSize(9,12))); log("Creating small islands..."); -for (let i = 0; i < 6 * Math.pow(scaleByMapSize(1, 3), 2); ++i) +for (let i = 0; i < 6 * Math.square(scaleByMapSize(1, 3)); ++i) { landAreaLen = landAreas.length; if (!landAreaLen) break; chosenPoint = pickRandom(landAreas); createAreas( new ChainPlacer( Math.floor(scaleByMapSize(4, 7)), Math.floor(scaleByMapSize(7, 10)), Math.floor(scaleByMapSize(16, 40)), 0.07, chosenPoint[0], chosenPoint[1], scaleByMapSize(22, 40)), [ new LayeredPainter([tMainTerrain, tMainTerrain], [2]), new SmoothElevationPainter(ELEVATION_SET, 3, 6), paintClass(clLand) ], avoidClasses(clLand, 3, clPlayer, 3), 1, 1); if (newIsland !== undefined) { var temp = []; for (var j = 0; j < landAreaLen; ++j) { let [x, z] = landAreas[j]; if (playerConstraint.allows(x, z) && landConstraint.allows(x, z)) temp.push([x, z]); } landAreas = temp; } } paintTerrainBasedOnHeight(1, 3, 0, tShore); paintTerrainBasedOnHeight(-8, 1, 2, tWater); for (var i = 0; i < numPlayers; i++) { var fx = fractionToTiles(playerX[i]); var fz = fractionToTiles(playerZ[i]); var ix = round(fx); var iz = round(fz); // create the city patch var cityRadius = radius/3; placer = new ClumpPlacer(PI*cityRadius*cityRadius, 0.6, 0.3, 10, ix, iz); var painter = new LayeredPainter([tRoadWild, tRoad], [1]); createArea(placer, painter, null); } log("Creating bumps..."); createAreas( new ClumpPlacer(scaleByMapSize(20, 50), 0.3, 0.06, 1), new SmoothElevationPainter(ELEVATION_MODIFY, 2, 2), [avoidClasses(clPlayer, 0), stayClasses(clLand, 3)], scaleByMapSize(20, 100)); log("Creating hills..."); createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(4, 6)), Math.floor(scaleByMapSize(16, 40)), 0.5), [ new LayeredPainter([tCliff, tHill], [2]), new SmoothElevationPainter(ELEVATION_SET, 18, 2), paintClass(clHill) ], [avoidClasses(clPlayer, 2, clHill, 15), stayClasses(clLand, 0)], scaleByMapSize(4, 13)); // calculate desired number of trees for map (based on size) if (currentBiome() == "savanna") { var MIN_TREES = 200; var MAX_TREES = 1250; var P_FOREST = 0; } else if (currentBiome() == "tropic") { var MIN_TREES = 1000; var MAX_TREES = 6000; var P_FOREST = 0.52; } else { var MIN_TREES = 500; var MAX_TREES = 3000; var P_FOREST = 0.7; } var totalTrees = scaleByMapSize(MIN_TREES, MAX_TREES); var numForest = totalTrees * P_FOREST; var numStragglers = totalTrees * (1.0 - P_FOREST); log("Creating forests..."); var types = [ [[tForestFloor2, tMainTerrain, pForest1], [tForestFloor2, pForest1]], [[tForestFloor1, tMainTerrain, pForest2], [tForestFloor1, pForest2]] ]; if (currentBiome() != "savanna") { var size = numForest / (scaleByMapSize(3,6) * numPlayers); var num = floor(size / types.length); for (let type of types) createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), numForest / (num * Math.floor(scaleByMapSize(2, 5))), 0.5), [ new LayeredPainter(type, [2]), paintClass(clForest) ], [avoidClasses(clPlayer, 0, clForest, 10, clHill, 0), stayClasses(clLand, 6)], num); } RMS.SetProgress(50); log("Creating dirt patches..."); var numberOfPatches = scaleByMapSize(15, 45) * (currentBiome() == "savanna" ? 3 : 1); for (let size of [scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)]) createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), size, 0.5), [ new LayeredPainter([[tMainTerrain, tTier1Terrain], [tTier1Terrain, tTier2Terrain], [tTier2Terrain, tTier3Terrain]], [1, 1]), paintClass(clDirt) ], [avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 0), stayClasses(clLand, 6)], numberOfPatches); log("Creating grass patches..."); for (let size of [scaleByMapSize(2, 4), scaleByMapSize(3, 7), scaleByMapSize(5, 15)]) createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), size, 0.5), new TerrainPainter(tTier4Terrain), [avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 0), stayClasses(clLand, 6)], numberOfPatches); RMS.SetProgress(55); log("Creating stone mines..."); var group = new SimpleGroup([new SimpleObject(oStoneSmall, 0, 2, 0, 4), new SimpleObject(oStoneLarge, 1, 1, 0, 4)], true, clRock); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 1, clPlayer, 0, clRock, 10, clHill, 1), stayClasses(clLand, 5)], scaleByMapSize(4,16), 100 ); log("Creating small stone quarries..."); group = new SimpleGroup([new SimpleObject(oStoneSmall, 2,5, 1,3)], true, clRock); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 1, clPlayer, 0, clRock, 10, clHill, 1), stayClasses(clLand, 5)], scaleByMapSize(4,16), 100 ); log("Creating metal mines..."); group = new SimpleGroup([new SimpleObject(oMetalLarge, 1,1, 0,4)], true, clMetal); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 1, clPlayer, 0, clMetal, 10, clRock, 5, clHill, 1), stayClasses(clLand, 5)], scaleByMapSize(4,16), 100 ); RMS.SetProgress(65); log("Creating small decorative rocks..."); group = new SimpleGroup( [new SimpleObject(aRockMedium, 1,3, 0,1)], true ); createObjectGroupsDeprecated( group, 0, [avoidClasses(clForest, 0, clPlayer, 0, clHill, 0), stayClasses(clLand, 5)], scaleByMapSize(16, 262), 50 ); log("Creating large decorative rocks..."); group = new SimpleGroup( [new SimpleObject(aRockLarge, 1,2, 0,1), new SimpleObject(aRockMedium, 1,3, 0,2)], true ); createObjectGroupsDeprecated( group, 0, [avoidClasses(clForest, 0, clPlayer, 0, clHill, 0), stayClasses(clLand, 5)], scaleByMapSize(8, 131), 50 ); RMS.SetProgress(70); log("Creating deer..."); group = new SimpleGroup( [new SimpleObject(oMainHuntableAnimal, 5,7, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 0, clPlayer, 0, clHill, 1, clFood, 20), stayClasses(clLand, 5)], 3 * numPlayers, 50 ); RMS.SetProgress(75); log("Creating sheep..."); group = new SimpleGroup( [new SimpleObject(oSecondaryHuntableAnimal, 2,3, 0,2)], true, clFood ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 0, clPlayer, 0, clHill, 1, clFood, 20), stayClasses(clLand, 5)], 3 * numPlayers, 50 ); log("Creating fruit bush..."); group = new SimpleGroup( [new SimpleObject(oFruitBush, 5,7, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 0, clPlayer, 8, clHill, 1, clFood, 20), stayClasses(clLand, 5)], randIntInclusive(1, 4) * numPlayers + 2, 50 ); log("Creating fish..."); group = new SimpleGroup( [new SimpleObject(oFish, 2,3, 0,2)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clLand, 4, clForest, 2, clPlayer, 2, clHill, 2, clFood, 20), 25 * numPlayers, 60 ); RMS.SetProgress(85); log("Creating straggler trees..."); var types = [oTree1, oTree2, oTree4, oTree3]; var num = floor(numStragglers / types.length); for (let type of types) createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(type, 1, 1, 0, 3)], true, clForest), 0, [avoidClasses(clForest, 1, clHill, 1, clPlayer, 0, clMetal, 6, clRock, 6), stayClasses(clLand, 6)], num); var planetm = 1; if (currentBiome() == "tropic") planetm = 8; log("Creating small grass tufts..."); group = new SimpleGroup( [new SimpleObject(aGrassShort, 1,2, 0,1, -PI/8,PI/8)] ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clHill, 2, clPlayer, 2, clDirt, 0), stayClasses(clLand, 6)], planetm * scaleByMapSize(13, 200) ); RMS.SetProgress(90); log("Creating large grass tufts..."); group = new SimpleGroup( [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)] ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clHill, 2, clPlayer, 2, clDirt, 1, clForest, 0), stayClasses(clLand, 5)], planetm * scaleByMapSize(13, 200) ); RMS.SetProgress(95); log("Creating bushes..."); group = new SimpleGroup( [new SimpleObject(aBushMedium, 1,2, 0,2), new SimpleObject(aBushSmall, 2,4, 0,2)] ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clHill, 1, clPlayer, 1, clDirt, 1), stayClasses(clLand, 6)], planetm * scaleByMapSize(13, 200), 50 ); setSkySet(pickRandom(["cirrus", "cumulus", "sunny"])); setSunRotation(randFloat(0, TWO_PI)); setSunElevation(randFloat(PI/ 5, PI / 3)); setWaterWaviness(2); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/oasis.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/oasis.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/oasis.js (revision 20331) @@ -1,363 +1,363 @@ RMS.LoadLibrary("rmgen"); const tSand = ["desert_sand_dunes_100", "desert_dirt_cracks","desert_sand_smooth", "desert_dirt_rough", "desert_dirt_rough_2", "desert_sand_smooth"]; const tDune = ["desert_sand_dunes_50"]; const tForestFloor = "desert_forestfloor_palms"; const tDirt = ["desert_dirt_rough","desert_dirt_rough","desert_dirt_rough", "desert_dirt_rough_2", "desert_dirt_rocks_2"]; const tRoad = "desert_city_tile";; const tRoadWild = "desert_city_tile";; const tShoreBlend = "desert_sand_wet"; const tShore = "dirta"; const tWater = "desert_sand_wet"; const ePalmShort = "gaia/flora_tree_cretan_date_palm_short"; const ePalmTall = "gaia/flora_tree_cretan_date_palm_tall"; const eBush = "gaia/flora_bush_grapes"; const eCamel = "gaia/fauna_camel"; const eGazelle = "gaia/fauna_gazelle"; const eLion = "gaia/fauna_lion"; const eLioness = "gaia/fauna_lioness"; const eStoneMine = "gaia/geology_stonemine_desert_quarry"; const eMetalMine = "gaia/geology_metal_desert_slabs"; const aFlower1 = "actor|props/flora/decals_flowers_daisies.xml"; const aWaterFlower = "actor|props/flora/water_lillies.xml"; const aReedsA = "actor|props/flora/reeds_pond_lush_a.xml"; const aReedsB = "actor|props/flora/reeds_pond_lush_b.xml"; const aRock = "actor|geology/stone_desert_med.xml"; const aBushA = "actor|props/flora/bush_desert_dry_a.xml"; const aBushB = "actor|props/flora/bush_desert_dry_a.xml"; const aSand = "actor|particle/blowing_sand.xml"; const pForestMain = [tForestFloor + TERRAIN_SEPARATOR + ePalmShort, tForestFloor + TERRAIN_SEPARATOR + ePalmTall, tForestFloor]; const pOasisForestLight = [tForestFloor + TERRAIN_SEPARATOR + ePalmShort, tForestFloor + TERRAIN_SEPARATOR + ePalmTall, tForestFloor,tForestFloor,tForestFloor ,tForestFloor,tForestFloor,tForestFloor,tForestFloor]; InitMap(); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); var clPlayer = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clWater = createTileClass(); var clPassage = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); initTerrain(tSand); var [playerIDs, playerX, playerZ] = radialPlayerPlacement(); var placer = undefined; var fx = 0; var fz = 0; var ix =0; var iz = 0; for (var i = 0; i < numPlayers; i++) { var id = playerIDs[i]; log("Creating base for player " + id + "..."); var radius = scaleByMapSize(15,25); var elevation = 20; // get the x and z in tiles fx = fractionToTiles(playerX[i]); fz = fractionToTiles(playerZ[i]); ix = round(fx); iz = round(fz); addCivicCenterAreaToClass(ix, iz, clPlayer); // create the city patch var cityRadius = radius/3; placer = new ClumpPlacer(PI*cityRadius*cityRadius, 0.6, 0.3, 10, ix, iz); var painter = new LayeredPainter([tRoadWild, tRoad], [1]); createArea(placer, painter, null); placeCivDefaultEntities(fx, fz, id); placeDefaultChicken(fx, fz, clBaseResource); // create berry bushes var bbAngle = randFloat(0, TWO_PI); var bbDist = 12; var bbX = round(fx + bbDist * cos(bbAngle)); var bbZ = round(fz + bbDist * sin(bbAngle)); var group = new SimpleGroup( [new SimpleObject(eBush, 5,5, 0,3)], true, clBaseResource, bbX, bbZ ); createObjectGroup(group, 0); // create metal mine var mAngle = bbAngle; while(abs(mAngle - bbAngle) < PI/3) { mAngle = randFloat(0, TWO_PI); } var mDist = radius*1.3; var mX = round(fx + mDist * cos(mAngle)); var mZ = round(fz + mDist * sin(mAngle)); group = new SimpleGroup( [new SimpleObject(eMetalMine, 1,1, 0,0),new SimpleObject(aBushB, 1,1, 2,2), new SimpleObject(aBushA, 0,2, 1,3),new SimpleObject(ePalmShort, 2,2, 2,3),new SimpleObject(ePalmTall, 1,1, 2,2)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); mX = round(fx + mDist*1.5 * cos(mAngle + PI/1.578)); mZ = round(fz + mDist*1.5 * sin(mAngle + PI/1.578)); group = new SimpleGroup( [new SimpleObject(eMetalMine, 1,1, 0,0),new SimpleObject(aBushB, 1,1, 2,2), new SimpleObject(aBushA, 0,2, 1,3),new SimpleObject(ePalmShort, 2,2, 2,3),new SimpleObject(ePalmTall, 1,1, 2,2)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); // create stone mines mAngle += randFloat(PI/8, PI/4); mX = round(fx + mDist * cos(mAngle)); mZ = round(fz + mDist * sin(mAngle)); group = new SimpleGroup( [new SimpleObject(eStoneMine, 1,1, 0,2),new SimpleObject(aBushB, 1,1, 2,2), new SimpleObject(aBushA, 0,2, 1,3),new SimpleObject(ePalmShort, 2,2, 2,3),new SimpleObject(ePalmTall, 1,1, 2,2)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); mX = round(fx + mDist * 1.4 * cos(mAngle - PI /2.46)); mZ = round(fz + mDist * 1.4 * sin(mAngle - PI /2.46)); group = new SimpleGroup( [new SimpleObject(eStoneMine, 1,1, 0,2),new SimpleObject(aBushB, 1,1, 2,2), new SimpleObject(aBushA, 0,2, 3,3),new SimpleObject(ePalmShort, 2,2, 3,3),new SimpleObject(ePalmTall, 1,1, 3,3)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); // Create starting batches of wood let forestX = 0; let forestY = 0; let forestAngle = 0 let forestDist = radius * 1.2; do { forestAngle = Math.PI / 3 * randFloat(1, 2); forestX = Math.round(fx + forestDist * Math.cos(forestAngle)); forestY = Math.round(fz + forestDist * Math.sin(forestAngle)); } while ( !createArea( new ClumpPlacer(70, 1, 0.5, 10, forestX, forestY), [ new LayeredPainter([tForestFloor, pForestMain], [0]), paintClass(clBaseResource) ], avoidClasses(clBaseResource, 0))); // Creating the water patch explaining the forest do { var watAngle = forestAngle + randFloat((PI/3), (5*PI/3)); var watX = round(forestX + 6 * cos(watAngle)); var watY = round(forestY + 6 * sin(watAngle)); createObjectGroup( new SimpleGroup( [new SimpleObject(aFlower1, 1, 5, 0, 3)], true, undefined, Math.round(forestX + 3 * Math.cos(watAngle)), Math.round(forestY + 3 * Math.sin(watAngle))), 0); createObjectGroup( new SimpleGroup( [new SimpleObject(aReedsA, 1, 3, 0, 0)], true, undefined, Math.round(forestX + 5 * Math.cos(watAngle)), Math.round(forestY + 5 * Math.sin(watAngle))), 0); } while ( !createArea( new ClumpPlacer(60, 0.9, 0.4, 5, watX, watY), [ new LayeredPainter([tShore, tShoreBlend], [1]), new SmoothElevationPainter(ELEVATION_MODIFY, -5, 3) ], avoidClasses(clBaseResource, 0))); } RMS.SetProgress(20); log("Creating bumps..."); createAreas( new ClumpPlacer(scaleByMapSize(20, 50), 0.3, 0.06, 1), new SmoothElevationPainter(ELEVATION_MODIFY, 4, 3), avoidClasses(clPlayer, 10, clBaseResource, 6), scaleByMapSize(30, 70)); log("Creating dirt Patches..."); createAreas( new ClumpPlacer(80, 0.3, 0.06, 1), new TerrainPainter(tDirt), avoidClasses(clPlayer, 10, clBaseResource, 6), scaleByMapSize(15, 50)); log("Creating Dunes..."); createAreas( new ClumpPlacer(120, 0.3, 0.06, 1), [ new TerrainPainter(tDune), new SmoothElevationPainter(ELEVATION_MODIFY, 18, 30) ], avoidClasses(clPlayer, 10, clBaseResource, 6), scaleByMapSize(15, 50)); log("Creating actual oasis..."); var fx = fractionToTiles(0.5); var fz = fractionToTiles(0.5); createArea( - new ClumpPlacer(Math.pow(mapSize * 0.2, 2) * 1.1, 0.8, 0.2, 10, Math.round(fx), Math.round(fz)), + new ClumpPlacer(Math.square(mapSize * 0.2) * 1.1, 0.8, 0.2, 10, Math.round(fx), Math.round(fz)), [ new LayeredPainter([pOasisForestLight,tShoreBlend, tWater, tWater, tWater], [scaleByMapSize(6, 20), 3, 5, 2]), new SmoothElevationPainter(ELEVATION_SET, -3, 15), paintClass(clWater) ], null); RMS.SetProgress(50); if (mapSize > 150 && randBool()) { log("Creating path though the oasis..."); var pAngle = randFloat(0, TWO_PI); var px = round(fx) + round(fractionToTiles(0.13 * cos(pAngle))); var py = round(fz) + round(fractionToTiles(0.13 * sin(pAngle))); var pex = round(fx) + round(fractionToTiles(0.13 * -cos(pAngle))); var pey = round(fz) + round(fractionToTiles(0.13 * sin(pAngle + PI))); createArea( new PathPlacer(px, py, pex, pey, scaleByMapSize(7, 18), 0.4, 1, 0.2, 0), [ new TerrainPainter(tSand), new SmoothElevationPainter(ELEVATION_MODIFY, 4, 5), paintClass(clPassage) ], null); } log("Creating some straggler trees around the Passage..."); var group = new SimpleGroup([new SimpleObject(ePalmTall, 1,1, 0,0),new SimpleObject(ePalmShort, 1, 2, 1, 2), new SimpleObject(aBushA, 0,2, 1,3)], true, clForest); createObjectGroupsDeprecated(group, 0, stayClasses(clPassage, 1), scaleByMapSize(60, 250), 100); log("Creating stone mines..."); group = new SimpleGroup([new SimpleObject(eStoneMine, 1,1, 0,0),new SimpleObject(ePalmShort, 1,2, 3,3),new SimpleObject(ePalmTall, 0,1, 3,3) ,new SimpleObject(aBushB, 1,1, 2,2), new SimpleObject(aBushA, 0,2, 1,3)], true, clRock); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 10, clForest, 1, clPlayer, 30, clRock, 10,clBaseResource, 2, clHill, 1), scaleByMapSize(6,25), 100 ); log("Creating metal mines..."); group = new SimpleGroup([new SimpleObject(eMetalMine, 1,1, 0,0),new SimpleObject(ePalmShort, 1,2, 2,3),new SimpleObject(ePalmTall, 0,1, 2,2) ,new SimpleObject(aBushB, 1,1, 2,2), new SimpleObject(aBushA, 0,2, 1,3)], true, clMetal); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 10, clForest, 1, clPlayer, 30, clMetal, 10,clBaseResource, 2, clRock, 10, clHill, 1), scaleByMapSize(6,25), 100 ); RMS.SetProgress(65); log("Creating small decorative rocks..."); group = new SimpleGroup( [new SimpleObject(aRock, 2,4, 0,2)], true, undefined ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 0, clPlayer, 10, clHill, 1, clFood, 20), 30, scaleByMapSize(10,50) ); RMS.SetProgress(70); log("Creating Camels..."); group = new SimpleGroup( [new SimpleObject(eCamel, 1,2, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 0, clPlayer, 10, clHill, 1, clFood, 20), 1 * numPlayers, 50 ); RMS.SetProgress(75); log("Creating Gazelles..."); group = new SimpleGroup( [new SimpleObject(eGazelle, 2,4, 0,2)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 0, clPlayer, 10, clHill, 1, clFood, 20), 1 * numPlayers, 50 ); RMS.SetProgress(85); log("Creating Oasis Animals..."); for (var p = 0; p < scaleByMapSize(5,30); p++) { var aAngle = randFloat(0, TWO_PI); var aDist = fractionToTiles(0.11); var animX = round(fx + aDist * cos(aAngle)); var animY = round(fz + aDist * sin(aAngle)); group = new RandomGroup( [new SimpleObject(eLion, 1,2, 0,4),new SimpleObject(eLioness, 1,2, 2,4),new SimpleObject(eGazelle, 4,6, 1,5),new SimpleObject(eCamel, 1,2, 1,5)], true, clFood, animX,animY); createObjectGroup(group, 0); } RMS.SetProgress(90); log("Creating bushes..."); group = new SimpleGroup( [new SimpleObject(aBushB, 1,2, 0,2), new SimpleObject(aBushA, 2,4, 0,2)] ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 2, clHill, 1, clPlayer, 1, clPassage, 1), scaleByMapSize(10, 40), 20 ); log ("Creating Sand blows and beautifications"); for (var sandx = 0; sandx < mapSize; sandx += 4) for (var sandz = 0; sandz < mapSize; sandz += 4) { if (getHeight(sandx,sandz) > 3.4) { if (randBool((getHeight(sandx,sandz) - 3.4) / 1.4)) { group = new SimpleGroup( [new SimpleObject(aSand, 0,1, 0,2)], true, undefined, sandx,sandz ); createObjectGroup(group, 0); } } else if (getHeight(sandx, sandz) > -2.5 && getHeight(sandx,sandz) < -1) { if (randBool(0.4)) { group = new SimpleGroup( [new SimpleObject(aWaterFlower, 1,4, 1,2)], true, undefined, sandx,sandz ); createObjectGroup(group, 0); } else if (randBool(0.7) && getHeight(sandx,sandz) < -1.9) { group = new SimpleGroup( [new SimpleObject(aReedsA, 5,12, 0,2),new SimpleObject(aReedsB, 5,12, 0,2)], true, undefined, sandx,sandz ); createObjectGroup(group, 0); } if (getTileClass(clPassage).countInRadius(sandx,sandz,2,true) > 0) { if (randBool(0.4)) { group = new SimpleGroup( [new SimpleObject(aWaterFlower, 1,4, 1,2)], true, undefined, sandx,sandz ); createObjectGroup(group, 0); } else if (randBool(0.7) && getHeight(sandx,sandz) < -1.9) { group = new SimpleGroup( [new SimpleObject(aReedsA, 5,12, 0,2),new SimpleObject(aReedsB, 5,12, 0,2)], true, undefined, sandx,sandz ); createObjectGroup(group, 0); } } } } setSkySet("sunny"); setSunColor(0.914,0.827,0.639); setSunRotation(PI/3); setSunElevation(0.5); setWaterColor(0, 0.227, 0.843); setWaterTint(0, 0.545, 0.859); setWaterWaviness(1.0); setWaterType("clap"); setWaterMurkiness(0.5); setTerrainAmbientColor(0.45, 0.5, 0.6); setUnitsAmbientColor(0.501961, 0.501961, 0.501961); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/ratumacos.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/ratumacos.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/ratumacos.js (revision 20331) @@ -1,287 +1,287 @@ // Location: 49.337248, 1.106107 // Map Width: 80km RMS.LoadLibrary("rmgen"); RMS.LoadLibrary("rmgen2"); RMS.LoadLibrary("rmbiome"); InitMap(); log("Initializing tile classes..."); setBiome("alpine"); initTileClasses(["shallowWater"]); log("Initializing environment..."); setSunColor(0.733, 0.746, 0.574); setWaterTint(0.224, 0.271, 0.270); setWaterColor(0.224, 0.271, 0.270); setWaterWaviness(8); setWaterMurkiness(0.87); setWaterType("lake"); setTerrainAmbientColor(0.521, 0.475, 0.322); setSunRotation(-1 * PI); setSunElevation(PI / 6.25); setFogFactor(0); setFogThickness(0); setFogColor(0.69, 0.616, 0.541); setPPEffect("hdr"); setPPContrast(0.67); setPPSaturation(0.42); setPPBloom(0.23); log("Initializing biome..."); g_Terrains.mainTerrain = "new_alpine_grass_d"; g_Terrains.forestFloor1 = "alpine_grass_d"; g_Terrains.forestFloor2 = "alpine_grass_c"; g_Terrains.tier1Terrain = "new_alpine_grass_c"; g_Terrains.tier2Terrain = "new_alpine_grass_b"; g_Terrains.tier3Terrain = "alpine_grass_a"; g_Terrains.tier4Terrain = "new_alpine_grass_e"; g_Terrains.roadWild = "new_alpine_citytile"; g_Terrains.road = "new_alpine_citytile"; g_Gaia.mainHuntableAnimal = "gaia/fauna_deer"; g_Gaia.secondaryHuntableAnimal = "gaia/fauna_pig"; g_Gaia.metalLarge = "gaia/geology_metal_alpine_slabs"; g_Gaia.metalSmall = "gaia/geology_metal_alpine"; g_Gaia.fish = "gaia/fauna_fish_tilapia"; g_Gaia.tree1 = "gaia/flora_tree_poplar"; g_Gaia.tree2 = "gaia/flora_tree_toona"; g_Gaia.tree3 = "gaia/flora_tree_apple"; g_Gaia.tree4 = "gaia/flora_tree_acacia"; g_Gaia.tree5 = "gaia/flora_tree_carob"; g_Decoratives.grass = "actor|props/flora/grass_soft_large.xml"; g_Decoratives.grassShort = "actor|props/flora/grass_tufts_a.xml"; g_Decoratives.rockLarge = "actor|geology/stone_granite_med.xml"; g_Decoratives.rockMedium = "actor|geology/stone_granite_small.xml"; g_Decoratives.bushMedium = "actor|props/flora/bush_tempe_a.xml"; g_Decoratives.bushSmall = "actor|props/flora/bush_tempe_b.xml"; g_Decoratives.reeds = "actor|props/flora/reeds_pond_lush_a.xml"; g_Decoratives.lillies = "actor|props/flora/water_lillies.xml"; RMS.SetProgress(5); log("Resetting terrain..."); resetTerrain(g_Terrains.mainTerrain, g_TileClasses.land, getMapBaseHeight()); RMS.SetProgress(10); log("Copying heightmap..."); var scale = paintHeightmap("ratumacos", (tile, x, y) => { if (tile.indexOf("mud_temp") >= 0) addToClass(x, y, g_TileClasses.mountain); }); RMS.SetProgress(30); log("Paint tile classes..."); paintTileClassBasedOnHeight(-3, -1, 3, g_TileClasses.shallowWater); paintTileClassBasedOnHeight(-100, -3, 3, g_TileClasses.water); RMS.SetProgress(40); log("Placing players..."); //Coordinate system of the heightmap var singleBases = [ [100, 265], [180, 260], [245, 220], [275, 145], [40, 165], [70, 95], [130, 50], [205, 45] ]; var strongholdBases = [ [65, 140], [180, 60], [260, 190], [120, 270] ]; randomPlayerPlacementAt(getTeamsArray(), singleBases, strongholdBases, scale, 0.06); RMS.SetProgress(50); log("Render gaia..."); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3, g_TileClasses.shallowWater, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] }, { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3, g_TileClasses.shallowWater, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] } ]); addElements(shuffleArray([ { "func": addSmallMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 6, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 30, g_TileClasses.water, 3, g_TileClasses.shallowWater, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["few"] }, { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 6, g_TileClasses.player, 30, g_TileClasses.rock, 30, g_TileClasses.metal, 20, g_TileClasses.water, 3, g_TileClasses.shallowWater, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 6, g_TileClasses.player, 30, g_TileClasses.rock, 30, g_TileClasses.metal, 20, g_TileClasses.water, 3, g_TileClasses.shallowWater, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 8, g_TileClasses.metal, 3, g_TileClasses.mountain, 6, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2, g_TileClasses.shallowWater, 2 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] } ])); addElements(shuffleArray([ { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 6, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.water, 3, g_TileClasses.shallowWater, 3 ], "sizes": ["huge"], "mixes": ["similar"], "amounts": ["tons"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 6, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.water, 3, g_TileClasses.shallowWater, 3 ], "sizes": ["huge"], "mixes": ["similar"], "amounts": ["tons"] }, { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.water, 3, g_TileClasses.shallowWater, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 4, g_TileClasses.metal, 2, g_TileClasses.mountain, 6, g_TileClasses.player, 12, g_TileClasses.rock, 2, g_TileClasses.water, 5, g_TileClasses.shallowWater, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["tons"] } ])); RMS.SetProgress(80); log("Adding lillies..."); createDecoration( [ [new SimpleObject(g_Decoratives.reeds, 1,3, 0,1)], [new SimpleObject(g_Decoratives.lillies, 1,2, 0,1)] ], [ - 200 * Math.pow(scaleByMapSize(3, 12), 2), - 100 * Math.pow(scaleByMapSize(3, 12), 2) + 200 * Math.square(scaleByMapSize(3, 12)), + 100 * Math.square(scaleByMapSize(3, 12)) ], stayClasses(g_TileClasses.shallowWater, 0) ); RMS.SetProgress(90); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/red_sea.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/red_sea.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/red_sea.js (revision 20331) @@ -1,295 +1,295 @@ // Coordinates: 21.824205, 40.289810 // Map Width: 2900km RMS.LoadLibrary("rmgen"); RMS.LoadLibrary("rmgen2"); RMS.LoadLibrary("rmbiome"); InitMap(); setBiome("desert"); initTileClasses(); setSunColor(0.733, 0.746, 0.574); setWindAngle(-0.43); setWaterTint(0.161, 0.286, 0.353); setWaterColor(0.129, 0.176, 0.259); setWaterWaviness(8); setWaterMurkiness(0.87); setWaterType("lake"); setTerrainAmbientColor(0.58, 0.443, 0.353); setSunRotation(PI * 1.1); setSunElevation(PI / 7); setFogFactor(0); setFogThickness(0); setFogColor(0.69, 0.616, 0.541); setPPEffect("hdr"); setPPContrast(0.67); setPPSaturation(0.42); setPPBloom(0.23); log("Initializing biome..."); g_Terrains.mainTerrain = "desert_dirt_rocks_2"; g_Terrains.forestFloor1 = "desert_grass_a_sand"; g_Terrains.forestFloor2 = "desert_grass_a_sand"; g_Terrains.tier1Terrain = "desert_dirt_rocks_2"; g_Terrains.tier2Terrain = "desert_dirt_rough"; g_Terrains.tier3Terrain = "desert_dirt_rough"; g_Terrains.tier4Terrain = "desert_sand_stones"; g_Terrains.roadWild = "road2"; g_Terrains.road = "road2"; g_Gaia.tree1 = "gaia/flora_tree_date_palm"; g_Gaia.tree2 = "gaia/flora_tree_senegal_date_palm"; g_Gaia.tree3 = "gaia/flora_tree_fig"; g_Gaia.tree4 = "gaia/flora_tree_cretan_date_palm_tall"; g_Gaia.tree5 = "gaia/flora_tree_cretan_date_palm_short"; g_Gaia.fruitBush = "gaia/flora_bush_grapes"; g_Decoratives.grass = "actor|props/flora/grass_field_dry_tall_b.xml"; g_Decoratives.grassShort = "actor|props/flora/grass_field_parched_short.xml"; g_Decoratives.rockLarge = "actor|geology/stone_desert_med.xml"; g_Decoratives.rockMedium = "actor|geology/stone_savanna_med.xml"; g_Decoratives.bushMedium = "actor|props/flora/bush_desert_dry_a.xml"; g_Decoratives.bushSmall = "actor|props/flora/bush_medit_sm_dry.xml"; g_Decoratives.dust = "actor|particle/dust_storm_reddish.xml"; log("Resetting terrain..."); resetTerrain(g_Terrains.mainTerrain, g_TileClasses.land, getMapBaseHeight()); RMS.SetProgress(10); log("Copying heightmap..."); var scale = paintHeightmap("red_sea", (tile, x, y) => { if (tile.indexOf("cliff") >= 0) addToClass(x, y, g_TileClasses.mountain); }); RMS.SetProgress(30); log("Rendering water..."); paintTileClassBasedOnHeight(-100, -1, 3, g_TileClasses.water); RMS.SetProgress(40); log("Placing players..."); // Coordinate system of the heightmap var singleBases = [ [175, 30], [45, 210], [280, 180], [180, 180], [230, 115], [130, 280], [200, 253], [90, 115], [45, 45] ]; var strongholdBases = [ [50, 160], [100, 50], [170, 260], [260, 160] ]; randomPlayerPlacementAt(getTeamsArray(), singleBases, strongholdBases, scale, 0.04); RMS.SetProgress(50); log("Adding mines and forests..."); addElements(shuffleArray([ { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 10, g_TileClasses.metal, 20, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal", "many"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 3, g_TileClasses.mountain, 2, g_TileClasses.player, 30, g_TileClasses.rock, 20, g_TileClasses.metal, 10, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal", "many"] }, { "func": addForests, "avoid": [ g_TileClasses.berries, 3, g_TileClasses.forest, 20, g_TileClasses.metal, 3, g_TileClasses.mountain, 3, g_TileClasses.player, 20, g_TileClasses.rock, 3, g_TileClasses.water, 2 ], "sizes": ["big"], "mixes": ["similar"], "amounts": ["few"] } ])); RMS.SetProgress(60); log("Ensure initial forests..."); addElements([{ "func": addForests, "avoid": [ g_TileClasses.berries, 2, g_TileClasses.forest, 25, g_TileClasses.metal, 3, g_TileClasses.mountain, 5, g_TileClasses.player, 15, g_TileClasses.rock, 3, g_TileClasses.water, 2 ], "sizes": ["small"], "mixes": ["similar"], "amounts": ["tons"] }]); RMS.SetProgress(65); log("Adding berries and animals..."); addElements(shuffleArray([ { "func": addBerries, "avoid": [ g_TileClasses.berries, 30, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal", "many"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 2, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addFish, "avoid": [ g_TileClasses.fish, 12, g_TileClasses.player, 8 ], "stay": [g_TileClasses.water, 4], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] }, { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 15, g_TileClasses.metal, 2, g_TileClasses.mountain, 1, g_TileClasses.player, 20, g_TileClasses.rock, 2, g_TileClasses.water, 5 ], "sizes": ["normal"], "mixes": ["same"], "amounts": ["many"] } ])); RMS.SetProgress(70); log("Adding decoration..."); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["many"] }, { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "sizes": ["normal"], "mixes": ["similar"], "amounts": ["many"] } ]); RMS.SetProgress(80); log("Adding reeds..."); createObjectGroupsDeprecated( new SimpleGroup( [ new SimpleObject(g_Decoratives.reeds, 5, 12, 1, 4), new SimpleObject(g_Decoratives.rockMedium, 1, 2, 1, 5) ], true, g_TileClasses.dirt ), 0, [ stayClasses(g_TileClasses.water, 1), borderClasses(g_TileClasses.water, scaleByMapSize(2,8), scaleByMapSize(2,5)) ], scaleByMapSize(100, 1000), 500 ); RMS.SetProgress(85); log("Adding dust..."); createObjectGroupsDeprecated( new SimpleGroup( [new SimpleObject(g_Decoratives.dust, 1, 1, 1, 4)], false ), 0, [ stayClasses(g_TileClasses.dirt, 1), avoidClasses( g_TileClasses.player, 10, g_TileClasses.water, 3 ) ], - Math.pow(scaleByMapSize(5, 20), 2), + Math.square(scaleByMapSize(5, 20)), 500 ); RMS.SetProgress(90); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20331) @@ -1,693 +1,685 @@ const PI = Math.PI; const TWO_PI = 2 * Math.PI; const TERRAIN_SEPARATOR = "|"; const SEA_LEVEL = 20.0; const CELL_SIZE = 4; const HEIGHT_UNITS_PER_METRE = 92; const MIN_MAP_SIZE = 128; const MAX_MAP_SIZE = 512; 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) { return min + (max - min) * (g_Map.size - MIN_MAP_SIZE) / (MAX_MAP_SIZE - MIN_MAP_SIZE); } 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, placeArgs, retryFactor, amount, getResult, behaveDeprecated = false) { if (behaveDeprecated && !(placeArgs.placer instanceof SimpleGroup || placeArgs.placer instanceof RandomGroup)) warn("Deprecated version of createFoo should only be used for SimpleGroup and RandomGroup placers!"); let maxFail = amount * retryFactor; let results = []; let good = 0; let bad = 0; while (good < amount && bad <= maxFail) { let result = placeFunc(placeArgs); if (result !== undefined || behaveDeprecated) { ++good; if (getResult) results.push(result); } else ++bad; } return getResult ? results : good; } /** * Helper function for randomly placing areas and groups on the map. */ function randomizePlacerCoordinates(placer, halfMapSize) { if (!!g_MapSettings.CircularMap) { // Polar coordinates // Uniformly distributed on the disk let r = halfMapSize * Math.sqrt(randFloat(0, 1)); let theta = randFloat(0, 2 * PI); placer.x = Math.floor(r * Math.cos(theta)) + halfMapSize; placer.z = Math.floor(r * Math.sin(theta)) + halfMapSize; } else { // Rectangular coordinates placer.x = randIntExclusive(0, g_Map.size); placer.z = randIntExclusive(0, g_Map.size); } } /** * Helper function for randomly placing areas and groups in the given areas. */ function randomizePlacerCoordinatesFromAreas(placer, areas) { let pt = pickRandom(pickRandom(areas).points); placer.x = pt.x; placer.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(placer, player, constraint, amount, retryFactor = 10) { return createObjectGroups(placer, player, constraint, amount, retryFactor, true); } function createObjectGroupsByAreasDeprecated(placer, player, constraint, amount, retryFactor, areas) { return createObjectGroupsByAreas(placer, 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, behaveDeprecated = false) { let placeFunc = function (args) { randomizePlacerCoordinates(args.placer, args.halfMapSize); return g_Map.createArea(args.placer, args.painter, args.constraint); }; let args = { "placer": centeredPlacer, "painter": painter, "constraint": constraint, "halfMapSize": g_Map.size / 2 }; return retryPlacing(placeFunc, args, retryFactor, amount, true, behaveDeprecated); } /** * 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, behaveDeprecated = false) { if (!areas.length) return []; let placeFunc = function (args) { randomizePlacerCoordinatesFromAreas(args.placer, args.areas); return g_Map.createArea(args.placer, args.painter, args.constraint); }; let args = { "placer": centeredPlacer, "painter": painter, "constraint": constraint, "areas": areas, "halfMapSize": g_Map.size / 2 }; return retryPlacing(placeFunc, args, retryFactor, amount, true, behaveDeprecated); } /** * Attempts to place the given number of groups in random places of the map. * Returns the number of actually placed groups. */ function createObjectGroups(placer, player, constraint, amount, retryFactor = 10, behaveDeprecated = false) { let placeFunc = function (args) { randomizePlacerCoordinates(args.placer, args.halfMapSize); return createObjectGroup(args.placer, args.player, args.constraint); }; let args = { "placer": placer, "player": player, "constraint": constraint, "halfMapSize": getMapSize() / 2 - MAP_BORDER_WIDTH }; return retryPlacing(placeFunc, args, 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(placer, player, constraint, amount, retryFactor, areas, behaveDeprecated = false) { if (!areas.length) return 0; let placeFunc = function (args) { randomizePlacerCoordinatesFromAreas(args.placer, args.areas); return createObjectGroup(args.placer, args.player, args.constraint); }; let args = { "placer": placer, "player": player, "constraint": constraint, "areas": areas }; return retryPlacing(placeFunc, args, 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, terrain) { // convert terrain param into terrain object g_Map.placeTerrain(x, z, createTerrain(terrain)); } function initTerrain(tileClass) { g_Map.initTerrain(createTerrain(tileClass)); } 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]; } function createArea(placer, painter, constraint) { return g_Map.createArea(placer, painter, constraint); } function createObjectGroup(placer, player, constraint) { return g_Map.createObjectGroup(placer, player, constraint); } function getMapSize() { return g_Map.size; } function getMapArea() { return g_Map.size * g_Map.size; } 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; } /** * Sorts an array of player IDs by team index. Players without teams come first. * Randomize order for players of the same team. */ function sortPlayers(playerIndices) { return shuffleArray(playerIndices).sort((p1, p2) => getPlayerTeam(p1 - 1) - getPlayerTeam(p2 - 1)); } /** * Mix player indices but sort by team. * * @returns {Array} - every item is an array of player indices */ function sortAllPlayers() { let playerIDs = []; for (let i = 0; i < getNumPlayers(); ++i) playerIDs.push(i+1); return sortPlayers(playerIDs); } function primeSortPlayers(playerIndices) { if (!playerIndices.length) return []; let prime = []; for (let i = 0; i < Math.ceil(playerIndices.length / 2); ++i) { prime.push(playerIndices[i]); prime.push(playerIndices[playerIndices.length - 1 - i]); } return prime; } function primeSortAllPlayers() { return primeSortPlayers(sortAllPlayers()); } function radialPlayerPlacement(percentRadius = 0.35) { let playerIDs = sortAllPlayers(); let playerX = []; let playerZ = []; let playerAngle = []; let startAngle = randFloat(0, TWO_PI); for (let i = 0; i < getNumPlayers(); ++i) { playerAngle[i] = startAngle + i * TWO_PI / getNumPlayers(); playerX[i] = 0.5 + percentRadius * Math.cos(playerAngle[i]); playerZ[i] = 0.5 + percentRadius * Math.sin(playerAngle[i]); } return [playerIDs, playerX, playerZ, playerAngle, startAngle]; } /** * Returns an array of percent numbers indicating the player location on river maps. * For example [0.2, 0.2, 0.4, 0.4, 0.6, 0.6, 0.8, 0.8] for a 4v4 or * [0.25, 0.33, 0.5, 0.67, 0.75] for a 2v3. */ function placePlayersRiver() { let playerPos = []; let numPlayers = getNumPlayers(); let numPlayersEven = numPlayers % 2 == 0; for (let i = 0; i < numPlayers; ++i) { let currentPlayerEven = i % 2 == 0; let offsetDivident = numPlayersEven || currentPlayerEven ? (i + 1) % 2 : 0; let offsetDivisor = numPlayersEven ? 0 : currentPlayerEven ? +1 : -1; playerPos[i] = ((i - 1 + offsetDivident) / 2 + 1) / ((numPlayers + offsetDivisor) / 2 + 1); } return playerPos; } function getStartingEntities(player) { let civ = getCivCode(player); if (!g_CivData[civ] || !g_CivData[civ].StartEntities || !g_CivData[civ].StartEntities.length) { warn("Invalid or unimplemented civ '"+civ+"' specified, falling back to '" + FALLBACK_CIV + "'"); civ = FALLBACK_CIV; } return g_CivData[civ].StartEntities; } function getHeight(x, z) { return g_Map.getHeight(x, z); } function setHeight(x, z, height) { g_Map.setHeight(x, z, 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; } /** - * Returns the distance between 2 points - */ -function getDistance(x1, z1, x2, z2) -{ - return Math.pow(Math.pow(x1 - x2, 2) + Math.pow(z1 - z2, 2), 1/2); -} - -/** * Returns the angle of the vector between point 1 and point 2. * The angle is counterclockwise from the positive x axis. */ function getAngle(x1, z1, x2, z2) { return Math.atan2(z2 - z1, x2 - x1); } /** * Returns the gradient of the line between point 1 and 2 in the form dz/dx */ function getGradient(x1, z1, x2, z2) { if (x1 == x2 && z1 == z2) return 0; return (z1-z2)/(x1-x2); } function getTerrainTexture(x, y) { return g_Map.getTexture(x, y); } function addCivicCenterAreaToClass(ix, iz, tileClass) { addToClass(ix, iz, tileClass); addToClass(ix, iz + 5, tileClass); addToClass(ix, iz - 5, tileClass); addToClass(ix + 5, iz, tileClass); addToClass(ix - 5, iz, tileClass); } /** * Returns the order to go through the points for the shortest closed path (array of indices) * @param {array} [points] - Points to be sorted of the form { "x": x_value, "y": y_value } */ function getOrderOfPointsForShortestClosePath(points) { let order = []; let distances = []; if (points.length <= 3) { for (let i = 0; i < points.length; ++i) order.push(i); return order; } // Just add the first 3 points let pointsToAdd = clone(points); for (let i = 0; i < 3; ++i) { order.push(i); pointsToAdd.shift(i); if (i) - distances.push(getDistance(points[order[i]].x, points[order[i]].y, points[order[i - 1]].x, points[order[i - 1]].y)); + distances.push(Math.euclidDistance2D(points[order[i]].x, points[order[i]].y, points[order[i - 1]].x, points[order[i - 1]].y)); } - distances.push(getDistance( + distances.push(Math.euclidDistance2D( points[order[0]].x, points[order[0]].y, points[order[order.length - 1]].x, points[order[order.length - 1]].y)); // Add remaining points so the path lengthens the least let numPointsToAdd = pointsToAdd.length; for (let i = 0; i < numPointsToAdd; ++i) { let indexToAddTo; let minEnlengthen = Infinity; let minDist1 = 0; let minDist2 = 0; for (let k = 0; k < order.length; ++k) { - let dist1 = getDistance(pointsToAdd[0].x, pointsToAdd[0].y, points[order[k]].x, points[order[k]].y); - let dist2 = getDistance(pointsToAdd[0].x, pointsToAdd[0].y, points[order[(k + 1) % order.length]].x, points[order[(k + 1) % order.length]].y); + let dist1 = Math.euclidDistance2D(pointsToAdd[0].x, pointsToAdd[0].y, points[order[k]].x, points[order[k]].y); + let dist2 = Math.euclidDistance2D(pointsToAdd[0].x, pointsToAdd[0].y, points[order[(k + 1) % order.length]].x, points[order[(k + 1) % order.length]].y); let 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; } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/map.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/map.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/map.js (revision 20331) @@ -1,328 +1,328 @@ /** * Class for holding map data and providing basic API to change it * * @param {int} [size] - Size of the map in tiles * @param {float} [baseHeight] - Starting height of the map */ function Map(size, baseHeight) { // Size must be 0 to 1024, divisible by patches this.size = size; // Create 2D arrays for textures, object, and areas this.texture = []; this.terrainObjects = []; this.area = []; for (let i = 0; i < size; ++i) { // Texture IDs this.texture[i] = new Uint16Array(size); // Entities this.terrainObjects[i] = []; // Area IDs this.area[i] = new Uint16Array(size); for (let j = 0; j < size; ++j) this.terrainObjects[i][j] = []; } // Create 2D array for heightmap let mapSize = size; if (!TILE_CENTERED_HEIGHT_MAP) ++mapSize; this.height = []; for (let i = 0; i < mapSize; ++i) { this.height[i] = new Float32Array(mapSize); for (let j = 0; j < mapSize; ++j) this.height[i][j] = baseHeight; } // Create name <-> id maps for textures this.nameToID = {}; this.IDToName = []; // Array of objects (entitys/actors) this.objects = []; // Array of integers this.tileClasses = []; this.areaID = 0; // Starting entity ID, arbitrary number to leave some space for player entities this.entityCount = 150; } Map.prototype.initTerrain = function(baseTerrain) { for (let i = 0; i < this.size; ++i) for (let j = 0; j < this.size; ++j) baseTerrain.place(i, j); }; // Return ID of texture (by name) Map.prototype.getTextureID = function(texture) { if (texture in this.nameToID) return this.nameToID[texture]; // Add new texture let id = this.IDToName.length; this.nameToID[texture] = id; this.IDToName[id] = texture; return id; }; // Return next free entity ID Map.prototype.getEntityID = function() { return this.entityCount++; }; // Check bounds on tile map Map.prototype.validT = function(x, z, distance = 0) { distance += MAP_BORDER_WIDTH; if (g_MapSettings.CircularMap) { let halfSize = Math.floor(this.size / 2); - return Math.round(getDistance(x, z, halfSize, halfSize)) < halfSize - distance - 1; + return Math.round(Math.euclidDistance2D(x, z, halfSize, halfSize)) < halfSize - distance - 1; } else return x >= distance && z >= distance && x < this.size - distance && z < this.size - distance; }; // Check bounds on tile map Map.prototype.inMapBounds = function(x, z) { return x >= 0 && z >= 0 && x < this.size && z < this.size; }; // Check bounds on height map if TILE_CENTERED_HEIGHT_MAP==true then it's (size, size) otherwise (size + 1 by size + 1) Map.prototype.validH = function(x, z) { if (x < 0 || z < 0) return false; if (TILE_CENTERED_HEIGHT_MAP) return x < this.size && z < this.size; return x <= this.size && z <= this.size; }; // Check bounds on tile class Map.prototype.validClass = function(c) { return c >= 0 && c < this.tileClasses.length; }; Map.prototype.getTexture = function(x, z) { if (!this.validT(x, z)) throw new Error("getTexture: invalid tile position (" + x + ", " + z + ")"); return this.IDToName[this.texture[x][z]]; }; Map.prototype.setTexture = function(x, z, texture) { if (!this.validT(x, z)) throw new Error("setTexture: invalid tile position (" + x + ", " + z + ")"); this.texture[x][z] = this.getTextureID(texture); }; Map.prototype.getHeight = function(x, z) { if (!this.validH(x, z)) throw new Error("getHeight: invalid vertex position (" + x + ", " + z + ")"); return this.height[x][z]; }; Map.prototype.setHeight = function(x, z, height) { if (!this.validH(x, z)) throw new Error("setHeight: invalid vertex position (" + x + ", " + z + ")"); this.height[x][z] = height; }; Map.prototype.getTerrainObjects = function(x, z) { if (!this.validT(x, z)) throw new Error("getTerrainObjects: invalid tile position (" + x + ", " + z + ")"); return this.terrainObjects[x][z]; }; Map.prototype.setTerrainObject = function(x, z, object) { if (!this.validT(x, z)) throw new Error("setTerrainObject: invalid tile position (" + x + ", " + z + ")"); this.terrainObjects[x][z] = object; }; Map.prototype.placeTerrain = function(x, z, terrain) { terrain.place(x, z); }; Map.prototype.addObject = function(obj) { this.objects.push(obj); }; Map.prototype.createArea = function(placer, painter, constraint) { // Check for multiple painters if (painter instanceof Array) painter = new MultiPainter(painter); if (constraint === undefined || constraint === null) constraint = new NullConstraint(); else if (constraint instanceof Array) // Check for multiple constraints constraint = new AndConstraint(constraint); let points = placer.place(constraint); if (!points) return undefined; let newID = ++this.areaID; let area = new Area(points, newID); for (let p of points) this.area[p.x][p.z] = newID; painter.paint(area); return area; }; Map.prototype.createObjectGroup = function(placer, player, constraint) { // Check for null constraint if (constraint === undefined || constraint === null) constraint = new NullConstraint(); else if (constraint instanceof Array) constraint = new AndConstraint(constraint); return placer.place(player, constraint); }; Map.prototype.createTileClass = function() { let newID = this.tileClasses.length; this.tileClasses.push(new TileClass(this.size, newID)); return newID; }; // Get height taking into account terrain curvature Map.prototype.getExactHeight = function(x, z) { let xi = Math.min(Math.floor(x), this.size); let zi = Math.min(Math.floor(z), this.size); let xf = x - xi; let zf = z - zi; let h00 = this.height[xi][zi]; let h01 = this.height[xi][zi + 1]; let h10 = this.height[xi + 1][zi]; let h11 = this.height[xi + 1][zi + 1]; return (1 - zf) * ((1 - xf) * h00 + xf * h10) + zf * ((1 - xf) * h01 + xf * h11); }; // Converts from the tile centered height map to the corner based height map, used when TILE_CENTERED_HEIGHT_MAP = true Map.prototype.cornerHeight = function(x, z) { let count = 0; let sumHeight = 0; for (let dir of [[-1, -1], [-1, 0], [0, -1], [0, 0]]) if (this.validH(x + dir[0], z + dir[1])) { ++count; sumHeight += this.height[x + dir[0]][z + dir[1]]; } if (count == 0) return 0; return sumHeight / count; }; Map.prototype.getFullEntityList = function(rotateForMapExport = false) { // Change rotation from simple 2d to 3d befor giving to engine if (rotateForMapExport) for (let obj of this.objects) obj.rotation.y = PI / 2 - obj.rotation.y; // All non terrain objects let entities = this.objects; // Terrain objects e.g. trees let size = this.size; for (let x = 0; x < size; ++x) for (let z = 0; z < size; ++z) if (this.terrainObjects[x][z] !== undefined) entities.push(this.terrainObjects[x][z]); return entities; }; Map.prototype.getMapData = function() { let data = {}; // Convert 2D heightmap array to flat array // Flat because it's easier to handle by the engine let mapSize = this.size + 1; let height = new Uint16Array(mapSize * mapSize); for (let x = 0; x < mapSize; ++x) for (let z = 0; z < mapSize; ++z) { let currentHeight; if (TILE_CENTERED_HEIGHT_MAP) currentHeight = this.cornerHeight(x, z); else currentHeight = this.height[x][z]; // Correct height by SEA_LEVEL and prevent under/overflow in terrain data height[z * mapSize + x] = Math.max(0, Math.min(0xFFFF, Math.floor((currentHeight + SEA_LEVEL) * HEIGHT_UNITS_PER_METRE))); } data.height = height; data.seaLevel = SEA_LEVEL; // Terrain, map width in tiles data.size = this.size; // Get array of textures used in this map data.textureNames = this.IDToName; // Entities data.entities = this.getFullEntityList(true); log("Number of entities: "+ data.entities.length); // Convert 2D tile data to flat array let tileIndex = new Uint16Array(this.size * this.size); let tilePriority = new Uint16Array(this.size * this.size); for (let x = 0; x < this.size; ++x) for (let z = 0; z < this.size; ++z) { // TODO: For now just use the texture's index as priority, might want to do this another way tileIndex[z * this.size + x] = this.texture[x][z]; tilePriority[z * this.size + x] = this.texture[x][z]; } data.tileData = { "index": tileIndex, "priority": tilePriority }; return data; }; Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/wall_builder.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/wall_builder.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/wall_builder.js (revision 20331) @@ -1,1030 +1,1032 @@ //////////////////////////////////////////////////////////////////// // This file contains functionality to place walls on random maps // //////////////////////////////////////////////////////////////////// // To do: // Check if all wall placement methods work with wall elements with entity === undefined (some still might raise errors in that case) // Rename wall elements to fit the entity names so that entity = "structures/" + "civ + "_" + wallElement.type in the common case (as far as possible) // Perhaps add Roman army camp to style palisades and add upgraded/balanced default palisade fortress types matching civ default fortresses strength // Perhaps add further wall elements cornerInHalf, cornerOutHalf (banding PI/4) and adjust default fortress types to better fit in the octagonal territory of a civil center // Perhaps swap angle and width in WallElement class(?) definition // Adjust argument order to be always the same: // Coordinates (center/start/target) // Wall element arguments (wall/wallPart/fortressType/cornerElement) // playerId (optional, default is 0/gaia) // wallStyle (optional, default is the players civ/"palisades for gaia") // angle/orientation (optional, default is 0) // other (all optional) arguments especially those hard to define (wallPartsAssortment, maybe make an own function for it) // Some arguments don't clearly match to this concept: // endWithFirst (wall or other) // skipFirstWall (wall or other) // gateOccurence (wall or other) // numCorners (wall or other) // skipFirstWall (wall or other) // maxAngle (angle or other) // maxBendOff (angle or other, unused ATM!!!) // irregularity // maxTrys // Add treasures to wall style "others" // Adjust documentation // Perhaps rename "endLeft" to "start" and "endRight" to "end" // ?Use available civ-type wall elements rather than palisades: Remove "endLeft" and "endRight" as default wall elements and adjust default palisade fortress types? // ?Remove "endRight", "endLeft" and adjust generic fortress types palisades? // ?Think of something to enable splitting walls into two walls so more complex walls can be build and roads can have branches/crossroads? // ?Readjust placement angle for wall elements with bending when used in linear/circular walls by their bending? ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // WallElement class definition // // Concept: If placed unrotated the wall's course is towards positive Y (top) with "outside" right (+X) and "inside" left (-X) like unrotated entities has their drop-points right (in rmgen) // The course of the wall will be changed by corners (bending != 0) and so the "inside"/"outside" direction // // type Descriptive string, example: "wallLong". NOTE: Not really needed. Mainly for custom wall elements and to get the wall element type in code // entity Optional. Template name string of the entity to be placed, example: "structures/cart_wall_long". Default is undefined (No entity placed) // angle Optional. The angle (float) added to place the entity so "outside" is right when the wall element is placed unrotated. Default is 0 // width Optional. How far this wall element lengthens the wall (float), if unrotated the Y space needed. Default is 0 // indent Optional. The lateral indentation of the entity, drawn "inside" (positive values) or pushed "outside" (negative values). Default is 0 // bending Optional. How the course of the wall is changed after this element, positive is bending "in"/left/counter clockwise (like entity placement) // NOTE: Bending is not supported by all placement functions (see there) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function WallElement(type, entity, angle, width, indent, bending) { this.type = type; // Default wall element type documentation: // Lengthening straight blocking (mainly left/right symmetric) wall elements (Walls and wall fortifications) // "wall" A blocking straight wall element that mainly lengthens the wall, self-explanatory // "wallShort" self-explanatory // "wallLong" self-explanatory // "tower" A blocking straight wall element with damage potential (but for palisades) that slightly lengthens the wall, example: wall tower, palisade tower(No attack) // "wallFort" A blocking straight wall element with massive damage potential that lengthens the wall, example: fortress, palisade fort // Lengthening straight non/custom blocking (mainly left/right symmetric) wall elements (Gates and entries) // "gate" A blocking straight wall element with passability determined by owner, example: gate (Functionality not yet implemented) // "entry" A non-blocking straight wall element (same width as gate) but without an actual template or just a flag/column/obelisk // "entryTower" A non-blocking straight wall element (same width as gate) represented by a single (maybe indented) template, example: defence tower, wall tower, outpost, watchtower // "entryFort" A non-blocking straight wall element represented by a single (maybe indented) template, example: fortress, palisade fort // Bending wall elements (Wall corners) // "cornerIn" A wall element bending the wall by PI/2 "inside" (left, +, see above), example: wall tower, palisade curve // "cornerOut" A wall element bending the wall by PI/2 "outside" (right, -, see above), example: wall tower, palisade curve // "cornerHalfIn" A wall element bending the wall by PI/4 "inside" (left, +, see above), example: wall tower, palisade curve. NOTE: Not yet implemented // "cornerHalfOut" A wall element bending the wall by PI/4 "outside" (right, -, see above), example: wall tower, palisade curve. NOTE: Not yet implemented // Zero length straight indented (mainly left/right symmetric) wall elements (Outposts/watchtowers and non-defensive base structures) // "outpost" A zero-length wall element without bending far indented so it stands outside the wall, example: outpost, defence tower, watchtower // "house" A zero-length wall element without bending far indented so it stands inside the wall that grants population bonus, example: house, hut, longhouse // "barracks" A zero-length wall element without bending far indented so it stands inside the wall that grants unit production, example: barracks, tavern, ... this.entity = entity; this.angle = angle !== undefined ? angle : 0; this.width = width !== undefined ? width : 0; this.indent = indent !== undefined ? indent : 0; this.bending = bending !== undefined ? bending : 0; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Fortress class definition // // A "fortress" here is a closed wall build of multiple wall elements attached together defined in Fortress.wall // It's mainly the abstract shape defined in a Fortress instances wall because different styles can be used for it (see wallStyles) // // type Descriptive string, example: "tiny". Not really needed (WallTool.wallTypes["type string"] is used). Mainly for custom wall elements // wall Optional. Array of wall element strings. Can be set afterwards. Default is an epty array. // Example: ["entrance", "wall", "cornerIn", "wall", "gate", "wall", "entrance", "wall", "cornerIn", "wall", "gate", "wall", "cornerIn", "wall"] // centerToFirstElement Optional. Object with properties "x" and "y" representing a vector from the visual center to the first wall element. Default is undefined ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function Fortress(type, wall, centerToFirstElement) { this.type = type; // Only usefull to get the type of the actual fortress this.wall = wall !== undefined ? wall : []; this.centerToFirstElement = undefined; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // wallStyles data structure for default wall styles // // A wall style is an associative array with all wall elements of that style in it associated with the wall element type string // wallStyles holds all the wall styles within an associative array with the civ string or another descriptive strings as key // Examples: "athen", "rome_siege", "palisades", "fence", "road" //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// var wallStyles = {}; // Generic civ dependent wall style definition. "rome_siege" needs some tweek... var wallScaleByType = { "athen": 1.5, "brit": 1.5, "cart": 1.8, "gaul": 1.5, "iber": 1.5, "mace": 1.5, "maur": 1.5, "pers": 1.5, "ptol": 1.5, "rome": 1.5, "sele": 1.5, "spart": 1.5, "rome_siege": 1.5 }; for (var style in wallScaleByType) { var civ = style; if (style == "rome_siege") civ = "rome"; wallStyles[style] = { // Default wall elements "tower": new WallElement("tower", "structures/" + style + "_wall_tower", PI, wallScaleByType[style]), "endLeft": new WallElement("endLeft", "structures/" + style + "_wall_tower", PI, wallScaleByType[style]), // Same as tower. To be compatible with palisades... "endRight": new WallElement("endRight", "structures/" + style + "_wall_tower", PI, wallScaleByType[style]), // Same as tower. To be compatible with palisades... "cornerIn": new WallElement("cornerIn", "structures/" + style + "_wall_tower", 5/4*PI, 0, 0.35 * wallScaleByType[style], PI/2), // 2^0.5 / 4 ~= 0.35 ~= 1/3 "cornerOut": new WallElement("cornerOut", "structures/" + style + "_wall_tower", 3/4*PI, 0.71 * wallScaleByType[style], 0, -PI/2), // 2^0.5 / 2 ~= 0.71 ~= 2/3 "wallShort": new WallElement("wallShort", "structures/" + style + "_wall_short", 0, 2*wallScaleByType[style]), "wall": new WallElement("wall", "structures/" + style + "_wall_medium", 0, 4*wallScaleByType[style]), "wallMedium": new WallElement("wall", "structures/" + style + "_wall_medium", 0, 4*wallScaleByType[style]), "wallLong": new WallElement("wallLong", "structures/" + style + "_wall_long", 0, 6*wallScaleByType[style]), // Gate and entrance wall elements "gate": new WallElement("gate", "structures/" + style + "_wall_gate", PI, 6*wallScaleByType[style]), "entry": new WallElement("entry", undefined, 0, 6*wallScaleByType[style]), "entryTower": new WallElement("entryTower", "structures/" + civ + "_defense_tower", PI, 6*wallScaleByType[style], -4*wallScaleByType[style]), "entryFort": new WallElement("entryFort", "structures/" + civ + "_fortress", 0, 8*wallScaleByType[style], 6*wallScaleByType[style]), // Defensive wall elements with 0 width outside the wall "outpost": new WallElement("outpost", "structures/" + civ + "_outpost", PI, 0, -4*wallScaleByType[style]), "defenseTower": new WallElement("defenseTower", "structures/" + civ + "_defense_tower", PI, 0, -4*wallScaleByType[style]), // Base buildings wall elements with 0 width inside the wall "barracks": new WallElement("barracks", "structures/" + civ + "_barracks", PI, 0, 4.5*wallScaleByType[style]), "civilCentre": new WallElement("civilCentre", "structures/" + civ + "_civil_centre", PI, 0, 4.5*wallScaleByType[style]), "farmstead": new WallElement("farmstead", "structures/" + civ + "_farmstead", PI, 0, 4.5*wallScaleByType[style]), "field": new WallElement("field", "structures/" + civ + "_field", PI, 0, 4.5*wallScaleByType[style]), "fortress": new WallElement("fortress", "structures/" + civ + "_fortress", PI, 0, 4.5*wallScaleByType[style]), "house": new WallElement("house", "structures/" + civ + "_house", PI, 0, 4.5*wallScaleByType[style]), "market": new WallElement("market", "structures/" + civ + "_market", PI, 0, 4.5*wallScaleByType[style]), "storehouse": new WallElement("storehouse", "structures/" + civ + "_storehouse", PI, 0, 4.5*wallScaleByType[style]), "temple": new WallElement("temple", "structures/" + civ + "_temple", PI, 0, 4.5*wallScaleByType[style]), // Generic space/gap wall elements "space1": new WallElement("space1", undefined, 0, 1*wallScaleByType[style]), "space2": new WallElement("space2", undefined, 0, 2*wallScaleByType[style]), "space3": new WallElement("space3", undefined, 0, 3*wallScaleByType[style]), "space4": new WallElement("space4", undefined, 0, 4*wallScaleByType[style]) }; } // Add wall fortresses for all generic styles wallStyles.athen.wallFort = new WallElement("wallFort", "structures/athen_fortress", 2*PI/2, 5.1, 1.9); wallStyles.brit.wallFort = new WallElement("wallFort", "structures/brit_fortress", PI, 2.8); wallStyles.cart.wallFort = new WallElement("wallFort", "structures/cart_fortress", PI, 5.1, 1.6); wallStyles.gaul.wallFort = new WallElement("wallFort", "structures/gaul_fortress", PI, 4.2, 1.5); wallStyles.iber.wallFort = new WallElement("wallFort", "structures/iber_fortress", PI, 5, 0.2); wallStyles.mace.wallFort = new WallElement("wallFort", "structures/mace_fortress", 2*PI/2, 5.1, 1.9); wallStyles.maur.wallFort = new WallElement("wallFort", "structures/maur_fortress", PI, 5.5); wallStyles.pers.wallFort = new WallElement("wallFort", "structures/pers_fortress", PI, 5.6, 1.9); wallStyles.ptol.wallFort = new WallElement("wallFort", "structures/ptol_fortress", 2*PI/2, 5.1, 1.9); wallStyles.rome.wallFort = new WallElement("wallFort", "structures/rome_fortress", PI, 6.3, 2.1); wallStyles.sele.wallFort = new WallElement("wallFort", "structures/sele_fortress", 2*PI/2, 5.1, 1.9); wallStyles.spart.wallFort = new WallElement("wallFort", "structures/spart_fortress", 2*PI/2, 5.1, 1.9); // Adjust "rome_siege" style wallStyles.rome_siege.wallFort = new WallElement("wallFort", "structures/rome_army_camp", PI, 7.2, 2); wallStyles.rome_siege.entryFort = new WallElement("entryFort", "structures/rome_army_camp", PI, 12, 7); wallStyles.rome_siege.house = new WallElement("house", "structures/rome_tent", PI, 0, 4); // Add special wall styles not well to implement generic (and to show how custom styles can be added) wallScaleByType.palisades = 0.55; let gate = new WallElement("gate", "other/palisades_rocks_gate", PI, 3.6); wallStyles.palisades = { "wall": new WallElement("wall", "other/palisades_rocks_medium", 0, 2.3), "wallMedium": new WallElement("wall", "other/palisades_rocks_medium", 0, 2.3), "wallLong": new WallElement("wall", "other/palisades_rocks_long", 0, 3.5), "wallShort": new WallElement("wall", "other/palisades_rocks_short", 0, 1.2), "tower": new WallElement("tower", "other/palisades_rocks_tower", -PI/2, 0.7), "wallFort": new WallElement("wallFort", "other/palisades_rocks_fort", PI, 1.7), "gate": gate, "entry": new WallElement("entry", undefined, gate.angle, gate.width), "entryTower": new WallElement("entryTower", "other/palisades_rocks_watchtower", 0, gate.width, -3), "entryFort": new WallElement("entryFort", "other/palisades_rocks_fort", PI, 6, 3), "cornerIn": new WallElement("cornerIn", "other/palisades_rocks_curve", 3*PI/4, 2.1, 0.7, PI/2), "cornerOut": new WallElement("cornerOut", "other/palisades_rocks_curve", 5*PI/4, 2.1, -0.7, -PI/2), "outpost": new WallElement("outpost", "other/palisades_rocks_outpost", PI, 0, -2), "house": new WallElement("house", "other/celt_hut", PI, 0, 5), "barracks": new WallElement("barracks", "structures/gaul_tavern", PI, 0, 5), "endRight": new WallElement("endRight", "other/palisades_rocks_end", -PI/2, 0.2), "endLeft": new WallElement("endLeft", "other/palisades_rocks_end", PI/2, 0.2) }; // NOTE: This is not a wall style in the common sense. Use with care! wallStyles.road = { "short": new WallElement("road", "actor|props/special/eyecandy/road_temperate_short.xml", PI/2, 4.5), "long": new WallElement("road", "actor|props/special/eyecandy/road_temperate_long.xml", PI/2, 9.5), // Correct width by -2*indent to fit xStraicht/corner "cornerLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_corner.xml", -PI/2, 4.5-2*1.25, 1.25, PI/2), "cornerRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_corner.xml", 0, 4.5-2*1.25, -1.25, -PI/2), "curveLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_curve_small.xml", -PI/2, 4.5+2*0.2, -0.2, PI/2), "curveRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_curve_small.xml", 0, 4.5+2*0.2, 0.2, -PI/2), "start": new WallElement("road", "actor|props/special/eyecandy/road_temperate_end.xml", PI/2, 2), "end": new WallElement("road", "actor|props/special/eyecandy/road_temperate_end.xml", -PI/2, 2), "xStraight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_x.xml", 0, 4.5), "xLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_x.xml", 0, 4.5, 0, PI/2), "xRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_x.xml", 0, 4.5, 0, -PI/2), "tLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_T.xml", PI, 4.5, 1.25), "tRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_T.xml", 0, 4.5, -1.25), }; // NOTE: This is not a wall style in the common sense. Use with care! wallStyles.other = { "fence": new WallElement("fence", "other/fence_long", -PI/2, 3.1), "fence_medium": new WallElement("fence", "other/fence_long", -PI/2, 3.1), "fence_short": new WallElement("fence_short", "other/fence_short", -PI/2, 1.5), "fence_stone": new WallElement("fence_stone", "other/fence_stone", -PI/2, 2.5), "palisade": new WallElement("palisade", "other/palisades_rocks_short", 0, 1.2), "column": new WallElement("column", "other/column_doric", 0, 1), "obelisk": new WallElement("obelisk", "other/obelisk", 0, 2), "spike": new WallElement("spike", "other/palisades_angle_spike", -PI/2, 1), "bench": new WallElement("bench", "other/bench", PI/2, 1.5), "benchForTable": new WallElement("benchForTable", "other/bench", 0, 0.5), "table": new WallElement("table", "other/table_rectangle", 0, 1), "table_square": new WallElement("table_square", "other/table_square", PI/2, 1), "flag": new WallElement("flag", "special/rallypoint", PI, 1), "standing_stone": new WallElement("standing_stone", "gaia/special_ruins_standing_stone", PI, 1), "settlement": new WallElement("settlement", "gaia/special_settlement", PI, 6), "gap": new WallElement("gap", undefined, 0, 2), "gapSmall": new WallElement("gapSmall", undefined, 0, 1), "gapLarge": new WallElement("gapLarge", undefined, 0, 4), "cornerIn": new WallElement("cornerIn", undefined, 0, 0, 0, PI/2), "cornerOut": new WallElement("cornerOut", undefined, 0, 0, 0, -PI/2) }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // fortressTypes data structure for some default fortress types // // A fortress type is just an instance of the Fortress class with actually something in it // fortressTypes holds all the fortresses within an associative array with a descriptive string as key (e.g. matching the map size) // Examples: "tiny", "veryLarge" //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// var fortressTypes = {}; { let wallParts = { "tiny": ["gate", "tower", "wallShort", "cornerIn", "wallShort", "tower"], "small": ["gate", "tower", "wall", "cornerIn", "wall", "tower"], "medium": ["gate", "tower", "wallLong", "cornerIn", "wallLong", "tower"], "normal": ["gate", "tower", "wall", "cornerIn", "wall", "cornerOut", "wall", "cornerIn", "wall", "tower"], "large": ["gate", "tower", "wallLong", "cornerIn", "wallLong", "cornerOut", "wallLong", "cornerIn", "wallLong", "tower"], "veryLarge": ["gate", "tower", "wall", "cornerIn", "wall", "cornerOut", "wallLong", "cornerIn", "wallLong", "cornerOut", "wall", "cornerIn", "wall", "tower"], "giant": ["gate", "tower", "wallLong", "cornerIn", "wallLong", "cornerOut", "wallLong", "cornerIn", "wallLong", "cornerOut", "wallLong", "cornerIn", "wallLong", "tower"] }; for (let type in wallParts) { fortressTypes[type] = new Fortress(type); let wp = wallParts[type]; fortressTypes[type].wall = wp.concat(wp, wp, wp); } } // Setup some better looking semi default fortresses for "palisades" style for (let type in fortressTypes) { var newKey = type + "Palisades"; var oldWall = fortressTypes[type].wall; fortressTypes[newKey] = new Fortress(newKey); var fillTowersBetween = ["wallShort", "wall", "wallLong", "endLeft", "endRight", "cornerIn", "cornerOut"]; for (var j = 0; j < oldWall.length; j++) { fortressTypes[newKey].wall.push(oldWall[j]); // Only works if the first element is not in fillTowersBetween (e.g. entry or gate like it should be) if (j+1 < oldWall.length) if (fillTowersBetween.indexOf(oldWall[j]) > -1 && fillTowersBetween.indexOf(oldWall[j+1]) > -1) // ... > -1 means "exists" here fortressTypes[newKey].wall.push("tower"); } } // Setup some balanced (to civ type fortresses) semi default fortresses for "palisades" style // TODO // Add some "fortress types" for roads (will only work with style "road") { // ["start", "short", "xRight", "xLeft", "cornerLeft", "xStraight", "long", "xLeft", "xRight", "cornerRight", "tRight", "tLeft", "xRight", "xLeft", "curveLeft", "xStraight", "curveRight", "end"]; let roadTypes = { "road01": ["short", "curveLeft", "short", "curveLeft", "short", "curveLeft", "short", "curveLeft"], "road02": ["short", "cornerLeft", "short", "cornerLeft", "short", "cornerLeft", "short", "cornerLeft"], "road03": ["xStraight", "curveLeft", "xStraight", "curveLeft", "xStraight", "curveLeft", "xStraight", "curveLeft"], "road04": ["start", "curveLeft", "tRight", "cornerLeft", "tRight", "curveRight", "short", "xRight", "curveLeft", "xRight", "short", "cornerLeft", "tRight", "short", "curveLeft", "short", "tRight", "cornerLeft", "short", "xRight", "curveLeft", "xRight", "short", "curveRight", "tRight", "cornerLeft", "tRight", "curveLeft", "end"], "road05": ["start", "tLeft", "short", "xRight", "curveLeft", "xRight", "tRight", "cornerLeft", "tRight", "curveLeft", "short", "tRight", "cornerLeft", "xRight", "cornerLeft", "xRight", "short", "tRight", "curveLeft", "end"], }; for (let type in roadTypes) fortressTypes[type] = new Fortress(type, roadTypes[type]); } /////////////////////////////// // Define some helper functions /////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // getWallAlignment // // Returns a list of objects containing all information to place all the wall elements entities with placeObject (but the player ID) // Placing the first wall element at startX/startY placed with an angle given by orientation // An alignment can be used to get the "center" of a "wall" (more likely used for fortresses) with getCenterToFirstElement ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function getWallAlignment(startX, startY, wall, style, orientation) { // Graciously handle arguments if (wall === undefined) wall = []; if (!wallStyles.hasOwnProperty(style)) { warn("Function getWallAlignment: Unknown style: " + style + ' (falling back to "athen")'); style = "athen"; } orientation = orientation || 0; var alignment = []; var wallX = startX; var wallY = startY; for (var i = 0; i < wall.length; i++) { var element = wallStyles[style][wall[i]]; if (element === undefined && i == 0) warn("No valid wall element: " + wall[i]); // Indentation var placeX = wallX - element.indent * cos(orientation); var placeY = wallY - element.indent * sin(orientation); // Add wall elements entity placement arguments to the alignment alignment.push({ "x": placeX, "y": placeY, "entity": element.entity, "angle": orientation + element.angle }); // Preset vars for the next wall element if (i+1 < wall.length) { orientation += element.bending; var nextElement = wallStyles[style][wall[i+1]]; if (nextElement === undefined) warn("No valid wall element: " + wall[i+1]); var distance = (element.width + nextElement.width)/2; // Corrections for elements with indent AND bending var indent = element.indent; var bending = element.bending; if (bending !== 0 && indent !== 0) { // Indent correction to adjust distance distance += indent*sin(bending); // Indent correction to normalize indentation wallX += indent * cos(orientation); wallY += indent * sin(orientation); } // Set the next coordinates of the next element in the wall without indentation adjustment wallX -= distance * sin(orientation); wallY += distance * cos(orientation); } } return alignment; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // getCenterToFirstElement // // Center calculation works like getting the center of mass assuming all wall elements have the same "weight" // // It returns the vector from the center to the first wall element // Used to get centerToFirstElement of fortresses by default ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function getCenterToFirstElement(alignment) { var centerToFirstElement = {"x": 0, "y": 0}; for (var i = 0; i < alignment.length; i++) { centerToFirstElement.x -= alignment[i].x/alignment.length; centerToFirstElement.y -= alignment[i].y/alignment.length; } return centerToFirstElement; } ////////////////////////////////////////////////////////////////// // getWallLength // // NOTE: Does not support bending wall elements like corners! // e.g. used by placeIrregularPolygonalWall ////////////////////////////////////////////////////////////////// function getWallLength(wall, style) { // Graciously handle arguments if (wall === undefined) wall = []; if (!wallStyles.hasOwnProperty(style)) { warn("Function getWallLength: Unknown style: " + style + ' (falling back to "athen")'); style = "athen"; } var length = 0; for (var i = 0; i < wall.length; i++) length += wallStyles[style][wall[i]].width; return length; } ///////////////////////////////////////////// // Define the different wall placer functions ///////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // placeWall // // Places a wall with wall elements attached to another like determined by WallElement properties. // // startX, startY Where the first wall element should be placed // wall Array of wall element type strings. Example: ["endLeft", "wallLong", "tower", "wallLong", "endRight"] // style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia // playerId Optional. Number of the player the wall will be placed for. Default is 0 (gaia) // orientation Optional. Angle the first wall element is placed. Default is 0 // 0 means "outside" or "front" of the wall is right (positive X) like placeObject // It will then be build towards top/positive Y (if no bending wall elements like corners are used) // Raising orientation means the wall is rotated counter-clockwise like placeObject ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function placeWall(startX, startY, wall, style, playerId, orientation) { // Graciously handle arguments if (wall === undefined) wall = []; playerId = playerId || 0; if (!wallStyles.hasOwnProperty(style)) { if (playerId == 0) style = style || "palisades"; else style = getCivCode(playerId-1); } orientation = orientation || 0; // Get wall alignment var AM = getWallAlignment(startX, startY, wall, style, orientation); // Place the wall for (var iWall = 0; iWall < wall.length; iWall++) { var entity = AM[iWall].entity; if (entity !== undefined) placeObject(AM[iWall].x, AM[iWall].y, entity, playerId, AM[iWall].angle); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // placeCustomFortress // // Place a fortress (mainly a closed wall build like placeWall) centered at centerX/centerY // The fortress wall should always start with the main entrance (like "entry" or "gate") to get the orientation right (like placeObject) // // fortress An instance of Fortress with a wall defined // style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia // playerId Optional. Number of the player the wall will be placed for. Default is 0 (gaia) // orientation Optional. Angle the first wall element (should be a gate or entrance) is placed. Default is BUILDING_ORIENTATION ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function placeCustomFortress(centerX, centerY, fortress, style, playerId = 0, orientation = BUILDING_ORIENTATION) { // Graciously handle arguments fortress = fortress || fortressTypes.medium; if (!wallStyles.hasOwnProperty(style)) { if (playerId == 0) style = style || "palisades"; else style = getCivCode(playerId-1); } // Calculate center if fortress.centerToFirstElement is undefined (default) var centerToFirstElement = fortress.centerToFirstElement; if (centerToFirstElement === undefined) centerToFirstElement = getCenterToFirstElement(getWallAlignment(0, 0, fortress.wall, style)); // Placing the fortress wall var startX = centerX + centerToFirstElement.x * cos(orientation) - centerToFirstElement.y * sin(orientation); var startY = centerY + centerToFirstElement.y * cos(orientation) + centerToFirstElement.x * sin(orientation); placeWall(startX, startY, fortress.wall, style, playerId, orientation); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // placeFortress // // Like placeCustomFortress just it takes type (a fortress type string, has to be in fortressTypes) instead of an instance of Fortress /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function placeFortress(centerX, centerY, type, style, playerId, orientation) { // Graciously handle arguments type = type || "medium"; playerId = playerId || 0; if (!wallStyles.hasOwnProperty(style)) { if (playerId == 0) style = style || "palisades"; else style = getCivCode(playerId-1); } orientation = orientation || 0; // Call placeCustomFortress with the given arguments placeCustomFortress(centerX, centerY, fortressTypes[type], style, playerId, orientation); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // placeLinearWall // // Places a straight wall from a given coordinate to an other repeatedly using the wall parts. // // startX/startY Coordinate of the approximate beginning of the wall (Not the place of the first wall element) // targetX/targetY Coordinate of the approximate ending of the wall (Not the place of the last wall element) // wallPart Optional. An array of NON-BENDING wall element type strings. Default is ["tower", "wallLong"] // style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia // playerId Optional. Integer number of the player. Default is 0 (gaia) // endWithFirst Optional. A boolean value. If true the 1st wall element in the wallPart array will finalize the wall. Default is true // // TODO: Maybe add angle offset for more generic looking? ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function placeLinearWall(startX, startY, targetX, targetY, wallPart, style, playerId, endWithFirst) { // Setup optional arguments to the default wallPart = wallPart || ["tower", "wallLong"]; playerId = playerId || 0; if (!wallStyles.hasOwnProperty(style)) { if (playerId == 0) style = style || "palisades"; else style = getCivCode(playerId-1); } endWithFirst = typeof endWithFirst == "undefined" ? true : endWithFirst; // Check arguments for (var elementIndex = 0; elementIndex < wallPart.length; elementIndex++) { var bending = wallStyles[style][wallPart[elementIndex]].bending; if (bending != 0) warn("Bending is not supported by placeLinearWall but a bending wall element is used: " + wallPart[elementIndex] + " -> wallStyles[style][wallPart[elementIndex]].entity"); } // Setup number of wall parts - var totalLength = getDistance(startX, startY, targetX, targetY); + var totalLength = Math.euclidDistance2D(startX, startY, targetX, targetY); var wallPartLength = 0; for (var elementIndex = 0; elementIndex < wallPart.length; elementIndex++) wallPartLength += wallStyles[style][wallPart[elementIndex]].width; var numParts = 0; if (endWithFirst) numParts = ceil((totalLength - wallStyles[style][wallPart[0]].width) / wallPartLength); else numParts = ceil(totalLength / wallPartLength); // Setup scale factor var scaleFactor = 1; if (endWithFirst) scaleFactor = totalLength / (numParts * wallPartLength + wallStyles[style][wallPart[0]].width); else scaleFactor = totalLength / (numParts * wallPartLength); // Setup angle var wallAngle = getAngle(startX, startY, targetX, targetY); // NOTE: function "getAngle()" is about to be changed... var placeAngle = wallAngle - PI/2; // Place wall entities var x = startX; var y = startY; for (var partIndex = 0; partIndex < numParts; partIndex++) { for (var elementIndex = 0; elementIndex < wallPart.length; elementIndex++) { var wallEle = wallStyles[style][wallPart[elementIndex]]; // Width correction x += scaleFactor * wallEle.width/2 * cos(wallAngle); y += scaleFactor * wallEle.width/2 * sin(wallAngle); // Indent correction var placeX = x - wallEle.indent * sin(wallAngle); var placeY = y + wallEle.indent * cos(wallAngle); // Placement var entity = wallEle.entity; if (entity !== undefined) placeObject(placeX, placeY, entity, playerId, placeAngle + wallEle.angle); x += scaleFactor * wallEle.width/2 * cos(wallAngle); y += scaleFactor * wallEle.width/2 * sin(wallAngle); } } if (endWithFirst) { var wallEle = wallStyles[style][wallPart[0]]; x += scaleFactor * wallEle.width/2 * cos(wallAngle); y += scaleFactor * wallEle.width/2 * sin(wallAngle); var entity = wallEle.entity; if (entity !== undefined) placeObject(x, y, entity, playerId, placeAngle + wallEle.angle); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // placeCircularWall // // Place a circular wall of repeated wall elements given in the argument wallPart around centerX/centerY with the given radius // The wall can be opened forming more an arc than a circle if maxAngle < 2*PI // The orientation then determines where this open part faces (0 means right like unrotated building's drop-points) // // centerX/Y Coordinates of the circle's center // radius How wide the circle should be (approximate, especially if maxBendOff != 0) // wallPart Optional. An array of NON-BENDING wall element type strings. Default is ["tower", "wallLong"] // style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia // playerId Optional. Integer number of the player. Default is 0 (gaia) // orientation Optional. Where the open part of the (circular) arc should face (if maxAngle is < 2*PI). Default is 0 // maxAngle Optional. How far the wall should circumvent the center. Default is 2*PI (full circle) // endWithFirst Optional. Boolean. If true the 1st wall element in the wallPart array will finalize the wall. Default is false for full circles, else true // maxBendOff Optional. How irregular the circle should be. 0 means regular circle, PI/2 means very irregular. Default is 0 (regular circle) // // NOTE: Don't use wall elements with bending like corners! // TODO: Perhaps add eccentricity and maxBendOff functionality (untill now an unused argument) // TODO: Perhaps add functionality for spirals ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function placeCircularWall(centerX, centerY, radius, wallPart, style, playerId, orientation, maxAngle, endWithFirst, maxBendOff) { // Setup optional arguments to the default wallPart = wallPart || ["tower", "wallLong"]; playerId = playerId || 0; if (!wallStyles.hasOwnProperty(style)) { if (playerId == 0) style = style || "palisades"; else style = getCivCode(playerId-1); } orientation = orientation || 0; maxAngle = maxAngle || 2*PI; if (endWithFirst === undefined) endWithFirst = maxAngle < 2*PI - 0.001; // Can this be done better? maxBendOff = maxBendOff || 0; // Check arguments if (maxBendOff > PI/2 || maxBendOff < 0) warn("placeCircularWall maxBendOff sould satisfy 0 < maxBendOff < PI/2 (~1.5) but it is: " + maxBendOff); for (var elementIndex = 0; elementIndex < wallPart.length; elementIndex++) { var bending = wallStyles[style][wallPart[elementIndex]].bending; if (bending != 0) warn("Bending is not supported by placeCircularWall but a bending wall element is used: " + wallPart[elementIndex]); } // Setup number of wall parts var totalLength = maxAngle * radius; var wallPartLength = 0; for (var elementIndex = 0; elementIndex < wallPart.length; elementIndex++) wallPartLength += wallStyles[style][wallPart[elementIndex]].width; var numParts = 0; if (endWithFirst) numParts = ceil((totalLength - wallStyles[style][wallPart[0]].width) / wallPartLength); else numParts = ceil(totalLength / wallPartLength); // Setup scale factor var scaleFactor = 1; if (endWithFirst) scaleFactor = totalLength / (numParts * wallPartLength + wallStyles[style][wallPart[0]].width); else scaleFactor = totalLength / (numParts * wallPartLength); // Place wall entities var actualAngle = orientation + (2*PI - maxAngle) / 2; var x = centerX + radius*cos(actualAngle); var y = centerY + radius*sin(actualAngle); for (var partIndex = 0; partIndex < numParts; partIndex++) for (var elementIndex = 0; elementIndex < wallPart.length; elementIndex++) { var wallEle = wallStyles[style][wallPart[elementIndex]]; // Width correction var addAngle = scaleFactor * wallEle.width / radius; var targetX = centerX + radius * cos(actualAngle + addAngle); var targetY = centerY + radius * sin(actualAngle + addAngle); var placeX = x + (targetX - x)/2; var placeY = y + (targetY - y)/2; var placeAngle = actualAngle + addAngle/2; // Indent correction placeX -= wallEle.indent * cos(placeAngle); placeY -= wallEle.indent * sin(placeAngle); // Placement var entity = wallEle.entity; if (entity !== undefined) placeObject(placeX, placeY, entity, playerId, placeAngle + wallEle.angle); // Prepare for the next wall element actualAngle += addAngle; x = centerX + radius*cos(actualAngle); y = centerY + radius*sin(actualAngle); } if (endWithFirst) { var wallEle = wallStyles[style][wallPart[0]]; var addAngle = scaleFactor * wallEle.width / radius; var targetX = centerX + radius * cos(actualAngle + addAngle); var targetY = centerY + radius * sin(actualAngle + addAngle); var placeX = x + (targetX - x)/2; var placeY = y + (targetY - y)/2; var placeAngle = actualAngle + addAngle/2; placeObject(placeX, placeY, wallEle.entity, playerId, placeAngle + wallEle.angle); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // placePolygonalWall // // Place a polygonal wall of repeated wall elements given in the argument wallPart around centerX/centerY with the given radius // // centerX/Y Coordinates of the polygon's center // radius How wide the circle should be in which the polygon fits // wallPart Optional. An array of NON-BENDING wall element type strings. Default is ["wallLong", "tower"] // cornerWallElement Optional. Wall element to be placed at the polygon's corners. Default is "tower" // style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia // playerId Optional. Integer number of the player. Default is 0 (gaia) // orientation Optional. Angle from the center to the first linear wall part placed. Default is 0 (towards positive X/right) // numCorners Optional. How many corners the polygon will have. Default is 8 (matching a civ centers territory) // skipFirstWall Optional. Boolean. If the first linear wall part will be left opened as entrance. Default is true // // NOTE: Don't use wall elements with bending like corners! // TODO: Replace skipFirstWall with firstWallPart to enable gate/defended entrance placement // TODO: Check some arguments // TODO: Add eccentricity and perhaps make it just call placeIrregularPolygonalWall with irregularity = 0 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function placePolygonalWall(centerX, centerY, radius, wallPart, cornerWallElement, style, playerId, orientation, numCorners, skipFirstWall = true) { // Setup optional arguments to the default wallPart = wallPart || ["wallLong", "tower"]; cornerWallElement = cornerWallElement || "tower"; // Don't use wide elements for this. Not supported well... playerId = playerId || 0; if (!wallStyles.hasOwnProperty(style)) { if (playerId == 0) style = style || "palisades"; else style = getCivCode(playerId-1); } orientation = orientation || 0; numCorners = numCorners || 8; // Setup angles var angleAdd = 2*PI/numCorners; var angleStart = orientation - angleAdd/2; // Setup corners var corners = []; for (var i = 0; i < numCorners; i++) corners.push([centerX + radius*cos(angleStart + i*angleAdd), centerY + radius*sin(angleStart + i*angleAdd)]); // Place Corners and walls for (var i = 0; i < numCorners; i++) { var angleToCorner = getAngle(corners[i][0], corners[i][1], centerX, centerY); placeObject(corners[i][0], corners[i][1], wallStyles[style][cornerWallElement].entity, playerId, angleToCorner); if (!skipFirstWall || i != 0) placeLinearWall( // Adjustment to the corner element width (approximately) corners[i][0] + wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAdd/2), // startX corners[i][1] - wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAdd/2), // startY corners[(i+1)%numCorners][0] - wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAdd/2), // targetX corners[(i+1)%numCorners][1] + wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAdd/2), // targetY wallPart, style, playerId); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // placeIrregularPolygonalWall // // Place an irregular polygonal wall of some wall parts to choose from around centerX/centerY with the given radius // // centerX/Y Coordinates of the polygon's center // radius How wide the circle should be in which the polygon fits // cornerWallElement Optional. Wall element to be placed at the polygon's corners. Default is "tower" // style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia // playerId Optional. Integer number of the player. Default is 0 (gaia) // orientation Optional. Angle from the center to the first linear wall part placed. Default is 0 (towards positive X/right) // numCorners Optional. How many corners the polygon will have. Default is 8 (matching a civ centers territory) // irregularity Optional. How irregular the polygon will be. 0 means regular, 1 means VERY irregular. Default is 0.5 // skipFirstWall Optional. Boolean. If the first linear wall part will be left opened as entrance. Default is true // wallPartsAssortment Optional. An array of wall part arrays to choose from for each linear wall connecting the corners. Default is hard to describe ^^ // // NOTE: wallPartsAssortment is put to the end because it's hardest to set // NOTE: Don't use wall elements with bending like corners! // TODO: Replace skipFirstWall with firstWallPart to enable gate/defended entrance placement // TODO: Check some arguments // TODO: Perhaps add eccentricity //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function placeIrregularPolygonalWall(centerX, centerY, radius, cornerWallElement, style, playerId, orientation, numCorners, irregularity, skipFirstWall, wallPartsAssortment) { // Setup optional arguments playerId = playerId || 0; if (!wallStyles.hasOwnProperty(style)) { if (playerId == 0) style = style || "palisades"; else style = getCivCode(playerId-1); } // Generating a generic wall part assortment with each wall part including 1 gate lengthened by walls and towers // NOTE: It might be a good idea to write an own function for that... var defaultWallPartsAssortment = [["wallShort"], ["wall"], ["wallLong"], ["gate", "tower", "wallShort"]]; var centeredWallPart = ["gate"]; var extandingWallPartAssortment = [["tower", "wallLong"], ["tower", "wall"]]; defaultWallPartsAssortment.push(centeredWallPart); for (var i = 0; i < extandingWallPartAssortment.length; i++) { var wallPart = centeredWallPart; for (var j = 0; j < radius; j++) { if (j%2 == 0) wallPart = wallPart.concat(extandingWallPartAssortment[i]); else { extandingWallPartAssortment[i].reverse(); wallPart = extandingWallPartAssortment[i].concat(wallPart); extandingWallPartAssortment[i].reverse(); } defaultWallPartsAssortment.push(wallPart); } } // Setup optional arguments to the default wallPartsAssortment = wallPartsAssortment || defaultWallPartsAssortment; cornerWallElement = cornerWallElement || "tower"; // Don't use wide elements for this. Not supported well... style = style || "palisades"; playerId = playerId || 0; orientation = orientation || 0; numCorners = numCorners || randIntInclusive(5, 7); irregularity = irregularity || 0.5; skipFirstWall = skipFirstWall || false; // Setup angles var angleToCover = 2*PI; var angleAddList = []; for (var i = 0; i < numCorners; i++) { // Randomize covered angles. Variety scales down with raising angle though... angleAddList.push(angleToCover/(numCorners-i) * (1 + randFloat(-irregularity, irregularity))); angleToCover -= angleAddList[angleAddList.length - 1]; } // Setup corners var corners = []; var angleActual = orientation - angleAddList[0]/2; for (var i = 0; i < numCorners; i++) { corners.push([centerX + radius*cos(angleActual), centerY + radius*sin(angleActual)]); if (i < numCorners - 1) angleActual += angleAddList[i+1]; } // Setup best wall parts for the different walls (a bit confusing naming...) var wallPartLengths = []; var maxWallPartLength = 0; for (var partIndex = 0; partIndex < wallPartsAssortment.length; partIndex++) { var length = wallPartLengths[partIndex]; wallPartLengths.push(getWallLength(wallPartsAssortment[partIndex], style)); if (length > maxWallPartLength) maxWallPartLength = length; } var wallPartList = []; // This is the list of the wall parts to use for the walls between the corners, not to confuse with wallPartsAssortment! for (var i = 0; i < numCorners; i++) { var bestWallPart = []; // This is a simpel wall part not a wallPartsAssortment! var bestWallLength = 99999999; // NOTE: This is not exactly like the length the wall will be in the end. Has to be tweaked... - var wallLength = getDistance(corners[i][0], corners[i][1], corners[(i+1)%numCorners][0], corners[(i+1)%numCorners][1]); + var wallLength = Math.euclidDistance2D(corners[i][0], corners[i][1], corners[(i + 1) % numCorners][0], corners[(i + 1) % numCorners][1]); var numWallParts = ceil(wallLength/maxWallPartLength); for (var partIndex = 0; partIndex < wallPartsAssortment.length; partIndex++) { var linearWallLength = numWallParts*wallPartLengths[partIndex]; if (linearWallLength < bestWallLength && linearWallLength > wallLength) { bestWallPart = wallPartsAssortment[partIndex]; bestWallLength = linearWallLength; } } wallPartList.push(bestWallPart); } // Place Corners and walls for (var i = 0; i < numCorners; i++) { var angleToCorner = getAngle(corners[i][0], corners[i][1], centerX, centerY); placeObject(corners[i][0], corners[i][1], wallStyles[style][cornerWallElement].entity, playerId, angleToCorner); if (!skipFirstWall || i != 0) placeLinearWall( // Adjustment to the corner element width (approximately) corners[i][0] + wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAddList[i]/2), // startX corners[i][1] - wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAddList[i]/2), // startY corners[(i+1)%numCorners][0] - wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAddList[(i+1)%numCorners]/2), // targetX corners[(i+1)%numCorners][1] + wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAddList[(i+1)%numCorners]/2), // targetY wallPartList[i], style, playerId, false); } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // placeGenericFortress // // Places a generic fortress with towers at the edges connected with long walls and gates (entries until gates work) // This is the default Iberian civ bonus starting wall // // centerX/Y The approximate center coordinates of the fortress // radius The approximate radius of the wall to be placed // playerId Optional. Integer number of the player. Default is 0 (gaia) // style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia // irregularity Optional. Float between 0 (circle) and 1 (very spiky), default is 1/2 // gateOccurence Optional. Integer number, every n-th walls will be a gate instead. Default is 3 // maxTrys Optional. How often the function tries to find a better fitting shape at max. Default is 100 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function placeGenericFortress(centerX, centerY, radius, playerId, style, irregularity, gateOccurence, maxTrys) { // Setup optional arguments radius = radius || 20; playerId = playerId || 0; if (!wallStyles.hasOwnProperty(style)) { if (playerId == 0) style = style || "palisades"; else style = getCivCode(playerId - 1); } irregularity = irregularity || 1/2; gateOccurence = gateOccurence || 3; maxTrys = maxTrys || 100; // Setup some vars var startAngle = randFloat(0, 2*PI); var actualOffX = radius*cos(startAngle); var actualOffY = radius*sin(startAngle); var actualAngle = startAngle; var pointDistance = wallStyles[style].wallLong.width + wallStyles[style].tower.width; // Searching for a well fitting point derivation var tries = 0; var bestPointDerivation = undefined; var minOverlap = 1000; var overlap = undefined; while (tries < maxTrys && minOverlap > wallStyles[style].tower.width / 10) { var pointDerivation = []; var distanceToTarget = 1000; var targetReached = false; while (!targetReached) { var indent = randFloat(-irregularity*pointDistance, irregularity*pointDistance); var tmpAngle = getAngle(actualOffX, actualOffY, (radius + indent)*cos(actualAngle + (pointDistance / radius)), (radius + indent)*sin(actualAngle + (pointDistance / radius))); actualOffX += pointDistance*cos(tmpAngle); actualOffY += pointDistance*sin(tmpAngle); actualAngle = getAngle(0, 0, actualOffX, actualOffY); pointDerivation.push([actualOffX, actualOffY]); - distanceToTarget = getDistance(actualOffX, actualOffY, pointDerivation[0][0], pointDerivation[0][1]); + distanceToTarget = Math.euclidDistance2D(actualOffX, actualOffY, ...pointDerivation[0]); var numPoints = pointDerivation.length; if (numPoints > 3 && distanceToTarget < pointDistance) // Could be done better... { targetReached = true; - overlap = pointDistance - getDistance(pointDerivation[numPoints - 1][0], pointDerivation[numPoints - 1][1], pointDerivation[0][0], pointDerivation[0][1]); + overlap = pointDistance - Math.euclidDistance2D(...pointDerivation[numPoints - 1], ...pointDerivation[0]); if (overlap < minOverlap) { minOverlap = overlap; bestPointDerivation = pointDerivation; } } } tries++; } log("placeGenericFortress: Reduced overlap to " + minOverlap + " after " + tries + " tries"); // Place wall for (var pointIndex = 0; pointIndex < bestPointDerivation.length; pointIndex++) { var startX = centerX + bestPointDerivation[pointIndex][0]; var startY = centerY + bestPointDerivation[pointIndex][1]; var targetX = centerX + bestPointDerivation[(pointIndex + 1) % bestPointDerivation.length][0]; var targetY = centerY + bestPointDerivation[(pointIndex + 1) % bestPointDerivation.length][1]; var angle = getAngle(startX, startY, targetX, targetY); var wallElement = "wallLong"; if ((pointIndex + 1) % gateOccurence == 0) wallElement = "gate"; var entity = wallStyles[style][wallElement].entity; if (entity) - { - placeObject(startX + (getDistance(startX, startY, targetX, targetY)/2)*cos(angle), // placeX - startY + (getDistance(startX, startY, targetX, targetY)/2)*sin(angle), // placeY - entity, playerId, angle - PI/2 + wallStyles[style][wallElement].angle); - } + placeObject( + startX + (Math.euclidDistance2D(startX, startY, targetX, targetY) / 2) * Math.cos(angle), + startY + (Math.euclidDistance2D(startX, startY, targetX, targetY) / 2) * Math.sin(angle), + entity, + playerId, + angle - Math.PI / 2 + wallStyles[style][wallElement].angle); + // Place tower var startX = centerX + bestPointDerivation[(pointIndex + bestPointDerivation.length - 1) % bestPointDerivation.length][0]; var startY = centerY + bestPointDerivation[(pointIndex + bestPointDerivation.length - 1) % bestPointDerivation.length][1]; var angle = getAngle(startX, startY, targetX, targetY); placeObject( centerX + bestPointDerivation[pointIndex][0], centerY + bestPointDerivation[pointIndex][1], wallStyles[style].tower.entity, playerId, angle - PI/2 + wallStyles[style].tower.angle); } } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen2/gaia.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen2/gaia.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen2/gaia.js (revision 20331) @@ -1,1266 +1,1266 @@ var g_Props = { "barrels": "actor|props/special/eyecandy/barrels_buried.xml", "crate": "actor|props/special/eyecandy/crate_a.xml", "cart": "actor|props/special/eyecandy/handcart_1_broken.xml", "well": "actor|props/special/eyecandy/well_1_c.xml", "skeleton": "actor|props/special/eyecandy/skeleton.xml", }; /** * Create bluffs, i.e. a slope hill reachable from ground level. * Fill it with wood, mines, animals and decoratives. * * @param {Array} constraint - where to place them * @param {number} size - size of the bluffs (1.2 would be 120% of normal) * @param {number} deviation - degree of deviation from the defined size (0.2 would be 20% plus/minus) * @param {number} fill - size of map to fill (1.5 would be 150% of normal) * @param {number} baseHeight - elevation of the floor, making the bluff reachable */ function addBluffs(constraint, size, deviation, fill, baseHeight) { var constrastTerrain = g_Terrains.tier2Terrain; if (currentBiome() == "tropic") constrastTerrain = g_Terrains.dirt; if (currentBiome() == "autumn") constrastTerrain = g_Terrains.tier3Terrain; var count = fill * 15; var minSize = 5; var maxSize = 7; var elevation = 30; var spread = 100; for (var i = 0; i < count; ++i) { var offset = getRandomDeviation(size, deviation); var rendered = createAreas( new ChainPlacer(Math.floor(minSize * offset), Math.floor(maxSize * offset), Math.floor(spread * offset), 0.5), [ new LayeredPainter([g_Terrains.cliff, g_Terrains.mainTerrain, constrastTerrain], [2, 3]), new SmoothElevationPainter(ELEVATION_MODIFY, Math.floor(elevation * offset), 2), paintClass(g_TileClasses.bluff) ], constraint, 1); // Find the bounding box of the bluff if (rendered[0] === undefined) continue; var points = rendered[0].points; var corners = findCorners(points); // Seed an array the size of the bounding box var bb = createBoundingBox(points, corners); // Get a random starting position for the baseline and the endline var angle = randIntInclusive(0, 3); var opAngle = angle - 2; if (angle < 2) opAngle = angle + 2; // Find the edges of the bluff var baseLine; var endLine; // If we can't access the bluff, try different angles var retries = 0; var bluffCat = 2; while (bluffCat != 0 && retries < 5) { baseLine = findClearLine(bb, corners, angle, baseHeight); endLine = findClearLine(bb, corners, opAngle, baseHeight); bluffCat = unreachableBluff(bb, corners, baseLine, endLine); ++angle; if (angle > 3) angle = 0; opAngle = angle - 2; if (angle < 2) opAngle = angle + 2; ++retries; } // Inaccessible, turn it into a plateau if (bluffCat > 0) { removeBluff(points); continue; } // Create an entrance area by using a small margin var margin = 0.08; var ground = createTerrain(g_Terrains.mainTerrain); - var slopeLength = (1 - margin) * getDistance(baseLine.midX, baseLine.midZ, endLine.midX, endLine.midZ); + var slopeLength = (1 - margin) * Math.euclidDistance2D(baseLine.midX, baseLine.midZ, endLine.midX, endLine.midZ); // Adjust the height of each point in the bluff for (var p = 0; p < points.length; ++p) { var pt = points[p]; var dist = distanceOfPointFromLine(baseLine.x1, baseLine.z1, baseLine.x2, baseLine.z2, pt.x, pt.z); var curHeight = g_Map.getHeight(pt.x, pt.z); var newHeight = curHeight - curHeight * (dist / slopeLength) - 2; newHeight = Math.max(newHeight, endLine.height); if (newHeight <= endLine.height + 2 && g_Map.validT(pt.x, pt.z) && g_Map.getTexture(pt.x, pt.z).indexOf('cliff') > -1) ground.place(pt.x, pt.z); g_Map.setHeight(pt.x, pt.z, newHeight); } // Smooth out the ground around the bluff fadeToGround(bb, corners.minX, corners.minZ, endLine.height); } addElements([ { "func": addHills, "avoid": [ g_TileClasses.hill, 3, g_TileClasses.player, 20, g_TileClasses.valley, 2, g_TileClasses.water, 2 ], "stay": [g_TileClasses.bluff, 3], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": g_AllAmounts } ]); addElements([ { "func": addLayeredPatches, "avoid": [ g_TileClasses.dirt, 5, g_TileClasses.forest, 2, g_TileClasses.mountain, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 5], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); addElements([ { "func": addDecoration, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 5], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["normal"] } ]); addElements([ { "func": addProps, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.player, 12, g_TileClasses.prop, 40, g_TileClasses.water, 3 ], "stay": [ g_TileClasses.bluff, 7, g_TileClasses.mountain, 7 ], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["scarce"] } ]); addElements(shuffleArray([ { "func": addForests, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 18, g_TileClasses.metal, 5, g_TileClasses.mountain, 5, g_TileClasses.player, 20, g_TileClasses.rock, 5, g_TileClasses.water, 2 ], "stay": [g_TileClasses.bluff, 6], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["normal", "many", "tons"] }, { "func": addMetal, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 5, g_TileClasses.mountain, 2, g_TileClasses.player, 50, g_TileClasses.rock, 15, g_TileClasses.metal, 40, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 6], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] }, { "func": addStone, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 5, g_TileClasses.mountain, 2, g_TileClasses.player, 50, g_TileClasses.rock, 40, g_TileClasses.metal, 15, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 6], "sizes": ["normal"], "mixes": ["same"], "amounts": ["normal"] } ])); let savanna = currentBiome() == "savanna"; addElements(shuffleArray([ { "func": addStragglerTrees, "avoid": [ g_TileClasses.berries, 5, g_TileClasses.forest, 10, g_TileClasses.metal, 5, g_TileClasses.mountain, 1, g_TileClasses.player, 12, g_TileClasses.rock, 5, g_TileClasses.water, 5 ], "stay": [g_TileClasses.bluff, 6], "sizes": savanna ? ["big"] : g_AllSizes, "mixes": savanna ? ["varied"] : g_AllMixes, "amounts": savanna ? ["tons"] : ["normal", "many", "tons"] }, { "func": addAnimals, "avoid": [ g_TileClasses.animals, 20, g_TileClasses.forest, 5, g_TileClasses.mountain, 1, g_TileClasses.player, 20, g_TileClasses.rock, 5, g_TileClasses.metal, 5, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 6], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["normal", "many", "tons"] }, { "func": addBerries, "avoid": [ g_TileClasses.berries, 50, g_TileClasses.forest, 5, g_TileClasses.metal, 10, g_TileClasses.mountain, 2, g_TileClasses.player, 20, g_TileClasses.rock, 10, g_TileClasses.water, 3 ], "stay": [g_TileClasses.bluff, 6], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["normal", "many", "tons"] } ])); } /** * Add grass, rocks and bushes. */ function addDecoration(constraint, size, deviation, fill) { var offset = getRandomDeviation(size, deviation); var decorations = [ [ new SimpleObject(g_Decoratives.rockMedium, offset, 3 * offset, 0, offset) ], [ new SimpleObject(g_Decoratives.rockLarge, offset, 2 * offset, 0, offset), new SimpleObject(g_Decoratives.rockMedium, offset, 3 * offset, 0, 2 * offset) ], [ new SimpleObject(g_Decoratives.grassShort, offset, 2 * offset, 0, offset) ], [ new SimpleObject(g_Decoratives.grass, 2 * offset, 4 * offset, 0, 1.8 * offset), new SimpleObject(g_Decoratives.grassShort, 3 * offset, 6 * offset, 1.2 * offset, 2.5 * offset) ], [ new SimpleObject(g_Decoratives.bushMedium, offset, 2 * offset, 0, 2 * offset), new SimpleObject(g_Decoratives.bushSmall, 2 * offset, 4 * offset, 0, 2 * offset) ] ]; var baseCount = 1; if (currentBiome() == "tropic") baseCount = 8; var counts = [ scaleByMapSize(16, 262), scaleByMapSize(8, 131), baseCount * scaleByMapSize(13, 200), baseCount * scaleByMapSize(13, 200), baseCount * scaleByMapSize(13, 200) ]; for (var i = 0; i < decorations.length; ++i) { var decorCount = Math.floor(counts[i] * fill); var group = new SimpleGroup(decorations[i], true); createObjectGroupsDeprecated(group, 0, constraint, decorCount, 5); } } /** * Create varying elevations. * * @param {Array} constraint - avoid/stay-classes * * @param {Object} el - the element to be rendered, for example: * "class": g_TileClasses.hill, * "painter": [g_Terrains.mainTerrain, g_Terrains.mainTerrain], * "size": 1, * "deviation": 0.2, * "fill": 1, * "count": scaleByMapSize(4, 8), * "minSize": Math.floor(scaleByMapSize(3, 8)), * "maxSize": Math.floor(scaleByMapSize(5, 10)), * "spread": Math.floor(scaleByMapSize(10, 20)), * "minElevation": 6, * "maxElevation": 12, * "steepness": 1.5 */ function addElevation(constraint, el) { var count = el.fill * el.count; var minSize = el.minSize; var maxSize = el.maxSize; var spread = el.spread; var elType = ELEVATION_MODIFY; if (el.class == g_TileClasses.water) elType = ELEVATION_SET; var widths = []; // Allow for shore and cliff rendering for (var s = el.painter.length; s > 2; --s) widths.push(1); for (var i = 0; i < count; ++i) { var elevation = randIntExclusive(el.minElevation, el.maxElevation); var smooth = Math.floor(elevation / el.steepness); var offset = getRandomDeviation(el.size, el.deviation); var pMinSize = Math.floor(minSize * offset); var pMaxSize = Math.floor(maxSize * offset); var pSpread = Math.floor(spread * offset); var pSmooth = Math.abs(Math.floor(smooth * offset)); var pElevation = Math.floor(elevation * offset); pElevation = Math.max(el.minElevation, Math.min(pElevation, el.maxElevation)); pMinSize = Math.min(pMinSize, pMaxSize); pMaxSize = Math.min(pMaxSize, el.maxSize); pMinSize = Math.max(pMaxSize, el.minSize); pSmooth = Math.max(pSmooth, 1); createAreas( new ChainPlacer(pMinSize, pMaxSize, pSpread, 0.5), [ new LayeredPainter(el.painter, [widths.concat(pSmooth)]), new SmoothElevationPainter(elType, pElevation, pSmooth), paintClass(el.class) ], constraint, 1); } } /** * Create rolling hills. */ function addHills(constraint, size, deviation, fill) { addElevation(constraint, { "class": g_TileClasses.hill, "painter": [g_Terrains.mainTerrain, g_Terrains.mainTerrain], "size": size, "deviation": deviation, "fill": fill, "count": 8, "minSize": 5, "maxSize": 8, "spread": 20, "minElevation": 6, "maxElevation": 12, "steepness": 1.5 }); } /** * Create random lakes with fish in it. */ function addLakes(constraint, size, deviation, fill) { var lakeTile = g_Terrains.water; if (currentBiome() == "temperate" || currentBiome() == "tropic") lakeTile = g_Terrains.dirt; if (currentBiome() == "mediterranean") lakeTile = g_Terrains.tier2Terrain; if (currentBiome() == "autumn") lakeTile = g_Terrains.shore; addElevation(constraint, { "class": g_TileClasses.water, "painter": [lakeTile, lakeTile], "size": size, "deviation": deviation, "fill": fill, "count": 6, "minSize": 7, "maxSize": 9, "spread": 70, "minElevation": -15, "maxElevation": -2, "steepness": 1.5 }); addElements([ { "func": addFish, "avoid": [ g_TileClasses.fish, 12, g_TileClasses.hill, 8, g_TileClasses.mountain, 8, g_TileClasses.player, 8 ], "stay": [g_TileClasses.water, 7], "sizes": g_AllSizes, "mixes": g_AllMixes, "amounts": ["normal", "many", "tons"] } ]); var group = new SimpleGroup([new SimpleObject(g_Decoratives.rockMedium, 1, 3, 1, 3)], true, g_TileClasses.dirt); createObjectGroupsDeprecated(group, 0, [stayClasses(g_TileClasses.water, 1), borderClasses(g_TileClasses.water, 4, 3)], 1000, 100); group = new SimpleGroup([new SimpleObject(g_Decoratives.reeds, 10, 15, 1, 3), new SimpleObject(g_Decoratives.rockMedium, 1, 3, 1, 3)], true, g_TileClasses.dirt); createObjectGroupsDeprecated(group, 0, [stayClasses(g_TileClasses.water, 2), borderClasses(g_TileClasses.water, 4, 3)], 1000, 100); } /** * Universal function to create layered patches. */ function addLayeredPatches(constraint, size, deviation, fill) { var minRadius = 1; var maxRadius = Math.floor(scaleByMapSize(3, 5)); var count = fill * scaleByMapSize(15, 45); var patchSizes = [ scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21) ]; for (let patchSize of patchSizes) { var offset = getRandomDeviation(size, deviation); var patchMinRadius = Math.floor(minRadius * offset); var patchMaxRadius = Math.floor(maxRadius * offset); createAreas( new ChainPlacer(Math.min(patchMinRadius, patchMaxRadius), patchMaxRadius, Math.floor(patchSize * offset), 0.5), [ new LayeredPainter( [ [g_Terrains.mainTerrain, g_Terrains.tier1Terrain], [g_Terrains.tier1Terrain, g_Terrains.tier2Terrain], [g_Terrains.tier2Terrain, g_Terrains.tier3Terrain], [g_Terrains.tier4Terrain] ], [1, 1]), paintClass(g_TileClasses.dirt) ], constraint, count * offset); } } /** * Create steep mountains. */ function addMountains(constraint, size, deviation, fill) { addElevation(constraint, { "class": g_TileClasses.mountain, "painter": [g_Terrains.cliff, g_Terrains.hill], "size": size, "deviation": deviation, "fill": fill, "count": 8, "minSize": 2, "maxSize": 4, "spread": 100, "minElevation": 100, "maxElevation": 120, "steepness": 4 }); } /** * Create plateaus. */ function addPlateaus(constraint, size, deviation, fill) { var plateauTile = g_Terrains.dirt; if (currentBiome() == "snowy") plateauTile = g_Terrains.tier1Terrain; if (currentBiome() == "alpine" || currentBiome() == "savanna") plateauTile = g_Terrains.tier2Terrain; if (currentBiome() == "autumn") plateauTile = g_Terrains.tier4Terrain; addElevation(constraint, { "class": g_TileClasses.plateau, "painter": [g_Terrains.cliff, plateauTile], "size": size, "deviation": deviation, "fill": fill, "count": 15, "minSize": 2, "maxSize": 4, "spread": 200, "minElevation": 20, "maxElevation": 30, "steepness": 8 }); for (var i = 0; i < 40; ++i) { var hillElevation = randIntInclusive(4, 18); createAreas( new ChainPlacer(3, 15, 1, 0.5), [ new LayeredPainter([plateauTile, plateauTile], [3]), new SmoothElevationPainter(ELEVATION_MODIFY, hillElevation, hillElevation - 2), paintClass(g_TileClasses.hill) ], [ avoidClasses(g_TileClasses.hill, 7), stayClasses(g_TileClasses.plateau, 7) ], 1); } addElements([ { "func": addDecoration, "avoid": [ g_TileClasses.dirt, 15, g_TileClasses.forest, 2, g_TileClasses.player, 12, g_TileClasses.water, 3 ], "stay": [g_TileClasses.plateau, 8], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["tons"] }, { "func": addProps, "avoid": [ g_TileClasses.forest, 2, g_TileClasses.player, 12, g_TileClasses.prop, 40, g_TileClasses.water, 3 ], "stay": [g_TileClasses.plateau, 8], "sizes": ["normal"], "mixes": ["normal"], "amounts": ["scarce"] } ]); } /** * Place less usual decoratives like barrels or crates. */ function addProps(constraint, size, deviation, fill) { var offset = getRandomDeviation(size, deviation); var props = [ [ new SimpleObject(g_Props.skeleton, offset, 5 * offset, 0, 3 * offset + 2), ], [ new SimpleObject(g_Props.barrels, offset, 2 * offset, 2, 3 * offset + 2), new SimpleObject(g_Props.cart, 0, offset, 5, 2.5 * offset + 5), new SimpleObject(g_Props.crate, offset, 2 * offset, 2, 2 * offset + 2), new SimpleObject(g_Props.well, 0, 1, 2, 2 * offset + 2) ] ]; var baseCount = 1; var counts = [ scaleByMapSize(16, 262), scaleByMapSize(8, 131), baseCount * scaleByMapSize(13, 200), baseCount * scaleByMapSize(13, 200), baseCount * scaleByMapSize(13, 200) ]; // Add small props for (var i = 0; i < props.length; ++i) { var propCount = Math.floor(counts[i] * fill); var group = new SimpleGroup(props[i], true); createObjectGroupsDeprecated(group, 0, constraint, propCount, 5); } // Add decorative trees var trees = new SimpleObject(g_Decoratives.tree, 5 * offset, 30 * offset, 2, 3 * offset + 10); createObjectGroupsDeprecated(new SimpleGroup([trees], true), 0, constraint, counts[0] * 5 * fill, 5); } function addValleys(constraint, size, deviation, fill, baseHeight) { if (baseHeight < 6) return; let minElevation = Math.max(-baseHeight, 1 - baseHeight / (size * (deviation + 1))); var valleySlope = g_Terrains.tier1Terrain; var valleyFloor = g_Terrains.tier4Terrain; if (currentBiome() == "desert") { valleySlope = g_Terrains.tier3Terrain; valleyFloor = g_Terrains.dirt; } if (currentBiome() == "mediterranean") { valleySlope = g_Terrains.tier2Terrain; valleyFloor = g_Terrains.dirt; } if (currentBiome() == "alpine" || currentBiome() == "savanna") valleyFloor = g_Terrains.tier2Terrain; if (currentBiome() == "tropic") valleySlope = g_Terrains.dirt; if (currentBiome() == "autumn") valleyFloor = g_Terrains.tier3Terrain; addElevation(constraint, { "class": g_TileClasses.valley, "painter": [valleySlope, valleyFloor], "size": size, "deviation": deviation, "fill": fill, "count": 8, "minSize": 5, "maxSize": 8, "spread": 30, "minElevation": minElevation, "maxElevation": -2, "steepness": 4 }); } /** * Create huntable animals. */ function addAnimals(constraint, size, deviation, fill) { var groupOffset = getRandomDeviation(size, deviation); var animals = [ [new SimpleObject(g_Gaia.mainHuntableAnimal, 5 * groupOffset, 7 * groupOffset, 0, 4 * groupOffset)], [new SimpleObject(g_Gaia.secondaryHuntableAnimal, 2 * groupOffset, 3 * groupOffset, 0, 2 * groupOffset)] ]; for (let animal of animals) createObjectGroupsDeprecated( new SimpleGroup(animal, true, g_TileClasses.animals), 0, constraint, Math.floor(30 * fill), 50); } function addBerries(constraint, size, deviation, fill) { let groupOffset = getRandomDeviation(size, deviation); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(g_Gaia.fruitBush, 5 * groupOffset, 5 * groupOffset, 0, 3 * groupOffset)], true, g_TileClasses.berries), 0, constraint, Math.floor(50 * fill), 40); } function addFish(constraint, size, deviation, fill) { var groupOffset = getRandomDeviation(size, deviation); var fishes = [ [new SimpleObject(g_Gaia.fish, groupOffset, 2 * groupOffset, 0, 2 * groupOffset)], [new SimpleObject(g_Gaia.fish, 2 * groupOffset, 4 * groupOffset, 10 * groupOffset, 20 * groupOffset)] ]; for (let fish of fishes) createObjectGroupsDeprecated( new SimpleGroup(fish, true, g_TileClasses.fish), 0, constraint, Math.floor(40 * fill), 50); } function addForests(constraint, size, deviation, fill) { if (currentBiome() == "savanna") return; let treeTypes = [ [ g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree1, g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree2, g_Terrains.forestFloor2 ], [ g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree4, g_Terrains.forestFloor1 + TERRAIN_SEPARATOR + g_Gaia.tree5, g_Terrains.forestFloor1 ] ]; let forestTypes = [ [ [g_Terrains.forestFloor2, g_Terrains.mainTerrain, treeTypes[0]], [g_Terrains.forestFloor2, treeTypes[0]] ], [ [g_Terrains.forestFloor2, g_Terrains.mainTerrain, treeTypes[1]], [g_Terrains.forestFloor1, treeTypes[1]]], [ [g_Terrains.forestFloor1, g_Terrains.mainTerrain, treeTypes[0]], [g_Terrains.forestFloor2, treeTypes[0]]], [ [g_Terrains.forestFloor1, g_Terrains.mainTerrain, treeTypes[1]], [g_Terrains.forestFloor1, treeTypes[1]] ] ]; for (let forestType of forestTypes) { let offset = getRandomDeviation(size, deviation); createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5) * offset), Math.floor(50 * offset), 0.5), [ new LayeredPainter(forestType, [2]), paintClass(g_TileClasses.forest) ], constraint, 10 * fill); } } function addMetal(constraint, size, deviation, fill) { var offset = getRandomDeviation(size, deviation); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(g_Gaia.metalLarge, offset, offset, 0, 4 * offset)], true, g_TileClasses.metal), 0, constraint, 1 + 20 * fill, 100); } function addSmallMetal(constraint, size, mixes, amounts) { let deviation = getRandomDeviation(size, mixes); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(g_Gaia.metalSmall, 2 * deviation, 5 * deviation, deviation, 3 * deviation)], true, g_TileClasses.metal), 0, constraint, 1 + 20 * amounts, 100); } /** * Create stone mines. */ function addStone(constraint, size, deviation, fill) { var offset = getRandomDeviation(size, deviation); var mines = [ [ new SimpleObject(g_Gaia.stoneSmall, 0, 2 * offset, 0, 4 * offset), new SimpleObject(g_Gaia.stoneLarge, offset, offset, 0, 4 * offset) ], [ new SimpleObject(g_Gaia.stoneSmall, 2 * offset, 5 * offset, offset, 3 * offset) ] ]; for (let mine of mines) createObjectGroupsDeprecated( new SimpleGroup(mine, true, g_TileClasses.rock), 0, constraint, 1 + 20 * fill, 100); } /** * Create straggler trees. */ function addStragglerTrees(constraint, size, deviation, fill) { // Ensure minimum distribution on african biome if (currentBiome() == "savanna") { fill = Math.max(fill, 2); size = Math.max(size, 1); } var trees = [g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree4]; var treesPerPlayer = 40; var playerBonus = Math.max(1, (getNumPlayers() - 3) / 2); var offset = getRandomDeviation(size, deviation); var treeCount = treesPerPlayer * playerBonus * fill; var totalTrees = scaleByMapSize(treeCount, treeCount); var count = Math.floor(totalTrees / trees.length) * fill; var min = offset; var max = 4 * offset; var minDist = offset; var maxDist = 5 * offset; // More trees for the african biome if (currentBiome() == "savanna") { min = 3 * offset; max = 5 * offset; minDist = 2 * offset + 1; maxDist = 3 * offset + 2; } for (var i = 0; i < trees.length; ++i) { var treesMax = max; // Don't clump fruit trees if (i == 2 && (currentBiome() == "desert" || currentBiome() == "mediterranean")) treesMax = 1; min = Math.min(min, treesMax); var group = new SimpleGroup([new SimpleObject(trees[i], min, treesMax, minDist, maxDist)], true, g_TileClasses.forest); createObjectGroupsDeprecated(group, 0, constraint, count); } } /////////// // Terrain Helpers /////////// /** * Determine if the endline of the bluff is within the tilemap. * * @returns {Number} 0 if the bluff is reachable, otherwise a positive number */ function unreachableBluff(bb, corners, baseLine, endLine) { // If we couldn't find a slope line if (typeof baseLine.midX === "undefined" || typeof endLine.midX === "undefined") return 1; // If the end points aren't on the tilemap if (!g_Map.validT(endLine.x1, endLine.z1) && !g_Map.validT(endLine.x2, endLine.z2)) return 2; var minTilesInGroup = 1; var insideBluff = false; var outsideBluff = false; // If there aren't enough points in each row for (var x = 0; x < bb.length; ++x) { var count = 0; for (var z = 0; z < bb[x].length; ++z) { if (!bb[x][z].isFeature) continue; var valid = g_Map.validT(x + corners.minX, z + corners.minZ); if (valid) ++count; if (!insideBluff && valid) insideBluff = true; if (outsideBluff && valid) return 3; } // We're expecting the end of the bluff if (insideBluff && count < minTilesInGroup) outsideBluff = true; } var insideBluff = false; var outsideBluff = false; // If there aren't enough points in each column for (var z = 0; z < bb[0].length; ++z) { var count = 0; for (var x = 0; x < bb.length; ++x) { if (!bb[x][z].isFeature) continue; var valid = g_Map.validT(x + corners.minX, z + corners.minZ); if (valid) ++count; if (!insideBluff && valid) insideBluff = true; if (outsideBluff && valid) return 3; } // We're expecting the end of the bluff if (insideBluff && count < minTilesInGroup) outsideBluff = true; } // Bluff is reachable return 0; } /** * Remove the bluff class and turn it into a plateau. */ function removeBluff(points) { for (var i = 0; i < points.length; ++i) addToClass(points[i].x, points[i].z, g_TileClasses.mountain); } /** * Create an array of points the fill a bounding box around a terrain feature. */ function createBoundingBox(points, corners) { var bb = []; var width = corners.maxX - corners.minX + 1; var length = corners.maxZ - corners.minZ + 1; for (var w = 0; w < width; ++w) { bb[w] = []; for (var l = 0; l < length; ++l) { var curHeight = g_Map.getHeight(w + corners.minX, l + corners.minZ); bb[w][l] = { "height": curHeight, "isFeature": false }; } } // Define the coordinates that represent the bluff for (var p = 0; p < points.length; ++p) { var pt = points[p]; bb[pt.x - corners.minX][pt.z - corners.minZ].isFeature = true; } return bb; } /** * Flattens the ground touching a terrain feature. */ function fadeToGround(bb, minX, minZ, elevation) { var ground = createTerrain(g_Terrains.mainTerrain); for (var x = 0; x < bb.length; ++x) for (var z = 0; z < bb[x].length; ++z) { var pt = bb[x][z]; if (!pt.isFeature && nextToFeature(bb, x, z)) { var newEl = smoothElevation(x + minX, z + minZ); g_Map.setHeight(x + minX, z + minZ, newEl); ground.place(x + minX, z + minZ); } } } /** * Find a 45 degree line in a bounding box that does not intersect any terrain feature. */ function findClearLine(bb, corners, angle, baseHeight) { // Angle - 0: northwest; 1: northeast; 2: southeast; 3: southwest var z = corners.maxZ; var xOffset = -1; var zOffset = -1; switch(angle) { case 1: xOffset = 1; break; case 2: xOffset = 1; zOffset = 1; z = corners.minZ; break; case 3: zOffset = 1; z = corners.minZ; break; } var clearLine = {}; for (var x = corners.minX; x <= corners.maxX; ++x) { var x2 = x; var z2 = z; var clear = true; while (x2 >= corners.minX && x2 <= corners.maxX && z2 >= corners.minZ && z2 <= corners.maxZ) { var bp = bb[x2 - corners.minX][z2 - corners.minZ]; if (bp.isFeature && g_Map.validT(x2, z2)) { clear = false; break; } x2 = x2 + xOffset; z2 = z2 + zOffset; } if (clear) { var lastX = x2 - xOffset; var lastZ = z2 - zOffset; var midX = Math.floor((x + lastX) / 2); var midZ = Math.floor((z + lastZ) / 2); clearLine = { "x1": x, "z1": z, "x2": lastX, "z2": lastZ, "midX": midX, "midZ": midZ, "height": baseHeight }; } if (clear && (angle == 1 || angle == 2)) break; if (!clear && (angle == 0 || angle == 3)) break; } return clearLine; } /** * Returns the corners of a bounding box. */ function findCorners(points) { // Find the bounding box of the terrain feature var mapSize = getMapSize(); var minX = mapSize + 1; var minZ = mapSize + 1; var maxX = -1; var maxZ = -1; for (var p = 0; p < points.length; ++p) { var pt = points[p]; minX = Math.min(pt.x, minX); minZ = Math.min(pt.z, minZ); maxX = Math.max(pt.x, maxX); maxZ = Math.max(pt.z, maxZ); } return { "minX": minX, "minZ": minZ, "maxX": maxX, "maxZ": maxZ }; } /** * Finds the average elevation around a point. */ function smoothElevation(x, z) { var min = g_Map.getHeight(x, z); for (var xOffset = -1; xOffset <= 1; ++xOffset) for (var zOffset = -1; zOffset <= 1; ++zOffset) { var thisX = x + xOffset; var thisZ = z + zOffset; if (!g_Map.validH(thisX, thisZ)) continue; var height = g_Map.getHeight(thisX, thisZ); if (height < min) min = height; } return min; } /** * Determines if a point in a bounding box array is next to a terrain feature. */ function nextToFeature(bb, x, z) { for (var xOffset = -1; xOffset <= 1; ++xOffset) for (var zOffset = -1; zOffset <= 1; ++zOffset) { var thisX = x + xOffset; var thisZ = z + zOffset; if (thisX < 0 || thisX >= bb.length || thisZ < 0 || thisZ >= bb[x].length || thisX == 0 && thisZ == 0) continue; if (bb[thisX][thisZ].isFeature) return true; } return false; } /** * Returns a number within a random deviation of a base number. */ function getRandomDeviation(base, deviation) { return base + randFloat(-1, 1) * Math.min(base, deviation); } /** * Import a given digital elevation model. * Scale it to the mapsize and paint the textures specified by coordinate on it. * * @return the ratio of heightmap tiles per map size tiles */ function paintHeightmap(mapName, func = undefined) { /** * @property heightmap - An array with a square number of heights. * @property tilemap - The IDs of the palletmap to be painted for each heightmap tile. * @property pallet - The tile texture names used by the tilemap. */ let mapData = RMS.ReadJSONFile("maps/random/" + mapName + ".hmap"); let mapSize = getMapSize(); // Width of the map in terrain tiles let hmSize = Math.sqrt(mapData.heightmap.length); let scale = hmSize / (mapSize + 1); // There are mapSize + 1 vertices (each 1 tile is surrounded by 2x2 vertices) for (let x = 0; x <= mapSize; ++x) for (let y = 0; y <= mapSize; ++y) { let hmPoint = { "x": x * scale, "y": y * scale }; let hmTile = { "x": Math.floor(hmPoint.x), "y": Math.floor(hmPoint.y) }; let shift = { "x": 0, "y": 0 }; if (hmTile.x == 0) shift.x = 1; else if (hmTile.x == hmSize - 1) shift.x = - 2; else if (hmTile.x == hmSize - 2) shift.x = - 1; if (hmTile.y == 0) shift.y = 1; else if (hmTile.y == hmSize - 1) shift.y = - 2; else if (hmTile.y == hmSize - 2) shift.y = - 1; let neighbors = []; for (let localXi = 0; localXi < 4; ++localXi) for (let localYi = 0; localYi < 4; ++localYi) neighbors.push(mapData.heightmap[(hmTile.x + localXi + shift.x - 1) * hmSize + (hmTile.y + localYi + shift.y - 1)]); setHeight(x, y, bicubicInterpolation(hmPoint.x - hmTile.x - shift.x, hmPoint.y - hmTile.y - shift.y, ...neighbors) / scale); if (x < mapSize && y < mapSize) { let i = hmTile.x * hmSize + hmTile.y; let tile = mapData.pallet[mapData.tilemap[i]]; placeTerrain(x, y, tile); if (func) func(tile, x, y); } } return scale; } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen2/setup.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen2/setup.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen2/setup.js (revision 20331) @@ -1,620 +1,620 @@ var g_Amounts = { "scarce": 0.2, "few": 0.5, "normal": 1, "many": 1.75, "tons": 3 }; var g_Mixes = { "same": 0, "similar": 0.1, "normal": 0.25, "varied": 0.5, "unique": 0.75 }; var g_Sizes = { "tiny": 0.5, "small": 0.75, "normal": 1, "big": 1.25, "huge": 1.5, }; var g_AllAmounts = Object.keys(g_Amounts); var g_AllMixes = Object.keys(g_Mixes); var g_AllSizes = Object.keys(g_Sizes); var 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_TileClasses; /** * Adds an array of elements to the map. */ function addElements(elements) { for (let element of elements) element.func( [ avoidClasses.apply(null, element.avoid), stayClasses.apply(null, element.stay || null) ], pickSize(element.sizes), pickMix(element.mixes), pickAmount(element.amounts), element.baseHeight || 0); } /** * Converts "amount" terms to numbers. */ function pickAmount(amounts) { let amount = pickRandom(amounts); if (amount in g_Amounts) return g_Amounts[amount]; return g_Amounts.normal; } /** * Converts "mix" terms to numbers. */ function pickMix(mixes) { let mix = pickRandom(mixes); if (mix in g_Mixes) return g_Mixes[mix]; return g_Mixes.normal; } /** * Converts "size" terms to numbers. */ function pickSize(sizes) { let size = pickRandom(sizes); if (size in g_Sizes) return g_Sizes[size]; return g_Sizes.normal; } /** * Paints the entire map with the given terrain texture, tileclass and elevation. */ function resetTerrain(terrain, tileClass, elevation) { let center = Math.round(fractionToTiles(0.5)); createArea( new ClumpPlacer(getMapArea(), 1, 1, 1, center, center), [ new LayeredPainter([terrain], []), new SmoothElevationPainter(ELEVATION_SET, elevation, 1), paintClass(tileClass) ], null); } /** * Choose starting locations for all players. * * @param {string} type - "radial", "line", "stronghold", "random" * @param {number} distance - radial distance from the center of the map * @param {number} groupedDistance - space between players within a team * @param {number} startAngle - determined by the map that might want to place something between players * @returns {Array|undefined} - If successful, each element is an object that contains id, angle, x, z for each player */ function addBases(type, distance, groupedDistance, startAngle) { let playerIDs = sortAllPlayers(); let teamsArray = getTeamsArray(); switch(type) { case "line": return placeLine(teamsArray, distance, groupedDistance, startAngle); case "radial": return placeRadial(playerIDs, distance, startAngle); case "random": return placeRandom(playerIDs) || placeRadial(playerIDs, distance, startAngle); case "stronghold": return placeStronghold(teamsArray, distance, groupedDistance, startAngle); default: warn("Unknown base placement type:" + type); return undefined; } } /** * Create the base for a single player. * * @param {Object} player - contains id, angle, x, z * @param {boolean} walls - Whether or not iberian gets starting walls */ function createBase(player, walls = true) { var mapSize = getMapSize(); // 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); addCivicCenterAreaToClass(ix, iz, g_TileClasses.player); if (walls && mapSize > 192) placeCivDefaultEntities(fx, fz, player.id); else placeCivDefaultEntities(fx, fz, player.id, { 'iberWall': false }); // Create the city patch var radius = scaleByMapSize(15, 25); var cityRadius = radius / 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); // TODO: retry loops are needed as resources might conflict with neighboring ones // 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)); var 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)); placeDefaultChicken( fx, fz, g_TileClasses.baseResource, avoidClasses(g_TileClasses.baseResource, 4), g_Gaia.chicken ); // Create starting trees var num = currentBiome() == "savanna" ? 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; } placeDefaultDecoratives( fx, fz, g_Decoratives.grassShort, g_TileClasses.baseResource, radius, avoidClasses(g_TileClasses.baseResource, 4)); } /** * Return an array where each element is an array of playerIndices of a team. */ function getTeamsArray() { var numPlayers = getNumPlayers(); // Group players by team var teams = []; for (let 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 (let i = 0; i < numPlayers; ++i) if (getPlayerTeam(i) == -1) teams.push([i+1]); // Remove unused indices return teams.filter(team => true); } /** * Choose a random pattern for placing the bases of the players. */ function randomStartingPositionPattern(teamsArray) { var formats = ["radial"]; var mapSize = getMapSize(); var numPlayers = getNumPlayers(); // Enable stronghold if we have a few teams and a big enough map if (teamsArray.length >= 2 && numPlayers >= 4 && mapSize >= 256) formats.push("stronghold"); // Enable random if we have enough teams or enough players on a big enough map if (mapSize >= 256 && (teamsArray.length >= 3 || numPlayers > 4)) formats.push("random"); // Enable line if we have enough teams and players on a big enough map if (teamsArray.length >= 2 && numPlayers >= 4 && mapSize >= 384) formats.push("line"); return { "setup": pickRandom(formats), "distance": randFloat(0.2, 0.35), "separation": randFloat(0.05, 0.1) }; } /** * 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 * @param {number} startAngle - determined by the map that might want to place something between players. * * @returns {Array} - contains id, angle, x, z for every player */ function placeLine(teamsArray, distance, groupedDistance, startAngle) { var players = []; for (let i = 0; i < teamsArray.length; ++i) { var safeDist = distance; if (distance + teamsArray[i].length * groupedDistance > 0.45) safeDist = 0.45 - teamsArray[i].length * groupedDistance; var teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; // Create player base for (var p = 0; p < teamsArray[i].length; ++p) { players[teamsArray[i][p]] = { "id": teamsArray[i][p], "x": 0.5 + (safeDist + p * groupedDistance) * cos(teamAngle), "z": 0.5 + (safeDist + p * groupedDistance) * sin(teamAngle) }; createBase(players[teamsArray[i][p]], false); } } return players; } /** * Place players in a circle-pattern. * * @param {Array} playerIDs - order of playerIDs to be placed * @param {number} distance - radial distance from the center of the map * @param {number} startAngle - determined by the map that might want to place something between players */ function placeRadial(playerIDs, distance, startAngle) { let players = []; let numPlayers = getNumPlayers(); for (let i = 0; i < numPlayers; ++i) { let angle = startAngle + i * 2 * Math.PI / numPlayers; players[i] = { "id": playerIDs[i], "x": 0.5 + distance * cos(angle), "z": 0.5 + distance * sin(angle) }; createBase(players[i]); } return players; } /** * Choose arbitrary starting locations. */ function placeRandom(playerIDs) { var locations = []; var attempts = 0; var resets = 0; for (let i = 0; i < getNumPlayers(); ++i) { var playerAngle = randFloat(0, TWO_PI); // Distance from the center of the map in percent // Mapsize being used as a diameter, so 0.5 is the edge of the map var distance = randFloat(0, 0.42); var x = 0.5 + distance * cos(playerAngle); var z = 0.5 + distance * sin(playerAngle); // Minimum distance between initial bases must be a quarter of the map diameter - if (locations.some(loc => getDistance(x, z, loc.x, loc.z) < 0.25)) + if (locations.some(loc => Math.euclidDistance2D(x, z, loc.x, loc.z) < 0.25)) { --i; ++attempts; // Reset if we're in what looks like an infinite loop if (attempts > 100) { locations = []; i = -1; attempts = 0; ++resets; // If we only pick bad locations, stop trying to place randomly if (resets == 100) return undefined; } continue; } locations[i] = { "x": x, "z": z }; } let players = groupPlayersByLocations(playerIDs, locations); for (let player of players) createBase(player); return players; } /** * Pick locations from the given set so that teams end up grouped. * * @param {Array} playerIDs - sorted by teams. * @param {Array} locations - array of x/z pairs of possible starting locations. */ function groupPlayersByLocations(playerIDs, locations) { playerIDs = sortPlayers(playerIDs); let minDist = Infinity; let minLocations; // Of all permutations of starting locations, find the one where // the sum of the distances between allies is minimal, weighted by teamsize. heapsPermute(shuffleArray(locations).slice(0, playerIDs.length), function(permutation) { let dist = 0; let teamDist = 0; let teamSize = 0; for (let i = 1; i < playerIDs.length; ++i) { let team1 = g_MapSettings.PlayerData[playerIDs[i - 1]].Team; let team2 = g_MapSettings.PlayerData[playerIDs[i]].Team; ++teamSize; if (team1 != -1 && team1 == team2) - teamDist += getDistance(permutation[i - 1].x, permutation[i - 1].z, permutation[i].x, permutation[i].z); + teamDist += Math.euclidDistance2D(permutation[i - 1].x, permutation[i - 1].z, permutation[i].x, permutation[i].z); else { dist += teamDist / teamSize; teamDist = 0; teamSize = 0; } } if (teamSize) dist += teamDist / teamSize; if (dist < minDist) { minDist = dist; minLocations = permutation; } }); let players = []; for (let i = 0; i < playerIDs.length; ++i) { let player = minLocations[i]; player.id = playerIDs[i]; players.push(player); } return players; } /** * Place given players in a stronghold-pattern. * * @param teamsArray - each item is an array of playerIDs placed per stronghold * @param distance - radial distance from the center of the map * @param groupedDistance - distance between neighboring players * @param {number} startAngle - determined by the map that might want to place something between players */ function placeStronghold(teamsArray, distance, groupedDistance, startAngle) { var players = []; for (let i = 0; i < teamsArray.length; ++i) { var teamAngle = startAngle + (i + 1) * 2 * Math.PI / teamsArray.length; var fractionX = 0.5 + distance * cos(teamAngle); var fractionZ = 0.5 + distance * sin(teamAngle); var teamGroupDistance = groupedDistance; // If we have a team of above average size, make sure they're spread out if (teamsArray[i].length > 4) teamGroupDistance = Math.max(0.08, groupedDistance); // If we have a solo player, place them on the center of the team's location if (teamsArray[i].length == 1) teamGroupDistance = 0; // TODO: Ensure players are not placed outside of the map area, similar to placeLine // Create player base for (var p = 0; p < teamsArray[i].length; ++p) { var angle = startAngle + (p + 1) * 2 * Math.PI / teamsArray[i].length; players[teamsArray[i][p]] = { "id": teamsArray[i][p], "x": fractionX + teamGroupDistance * cos(angle), "z": fractionZ + teamGroupDistance * sin(angle) }; createBase(players[teamsArray[i][p]], false); } } return players; } /** * Places players either randomly or in a stronghold-pattern at a set of given heightmap coordinates. * * @param teamsArray - Array where each item is an array of playerIDs, possibly going to be grouped. * @param singleBases - pair of coordinates of the heightmap to place isolated bases. * @param singleBases - pair of coordinates of the heightmap to place team bases. * @param groupedDistance - distance between neighboring players. * @param func - A function called for every player base or stronghold placed. */ function randomPlayerPlacementAt(teamsArray, singleBases, strongholdBases, heightmapScale, groupedDistance, func) { let strongholdBasesRandom = shuffleArray(strongholdBases); let mapSize = getMapSize(); if (randBool(1/3) && mapSize >= 256 && teamsArray.length >= 2 && teamsArray.length < getNumPlayers() && teamsArray.length <= strongholdBasesRandom.length) { let startAngle = randFloat(0, 2 * Math.PI); for (let t = 0; t < teamsArray.length; ++t) { let tileX = Math.floor(strongholdBasesRandom[t][0] / heightmapScale); let tileY = Math.floor(strongholdBasesRandom[t][1] / heightmapScale); let x = tileX / mapSize; let z = tileY / mapSize; let team = teamsArray[t].map(playerID => ({ "id": playerID })); let players = []; if (func) func(tileX, tileY); for (let p = 0; p < team.length; ++p) { let angle = startAngle + (p + 1) * TWO_PI / team.length; players[p] = { "id": team[p].id, "x": x + groupedDistance * cos(angle), "z": z + groupedDistance * sin(angle) }; createBase(players[p], false); } } } else { let players = groupPlayersByLocations(sortAllPlayers(), singleBases.map(l => ({ "x": l[0] / heightmapScale / mapSize, "z": l[1] / heightmapScale / mapSize }))); for (let player of players) { if (func) func(Math.floor(player.x * mapSize), Math.floor(player.z * mapSize)); createBase(player); } } } /** * 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(); } Index: ps/trunk/binaries/data/mods/public/maps/random/schwarzwald.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/schwarzwald.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/schwarzwald.js (revision 20331) @@ -1,410 +1,411 @@ RMS.LoadLibrary('rmgen'); RMS.LoadLibrary("heightmap"); log('Initializing map...'); InitMap(); setSkySet("fog"); setFogFactor(0.35); setFogThickness(0.19); setWaterColor(0.501961, 0.501961, 0.501961); setWaterTint(0.25098, 0.501961, 0.501961); setWaterWaviness(0.5); setWaterType("clap"); setWaterMurkiness(0.75); setPPSaturation(0.37); setPPContrast(0.4); setPPBrightness(0.4); setPPEffect("hdr"); setPPBloom(0.4); var clPlayer = createTileClass(); var clPath = createTileClass(); var clForest = createTileClass(); var clWater = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var clOpen = createTileClass(); var templateStoneMine = 'gaia/geology_stonemine_alpine_quarry'; 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"; 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 baseTex = ['temp_road', 'temp_road_overgrown']; var terrainPath = ['temp_road', 'temp_road_overgrown']; var tWater = ['dirt_brown_d']; var tWaterBorder = ['dirt_brown_d']; var mapSize = getMapSize(); var mapRadius = mapSize/2; var playableMapRadius = mapRadius - 5; var mapCenterX = mapRadius; var mapCenterZ = mapRadius; 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 = []; var playerStartLocZ = []; var playerAngle = []; var playerAngleStart = randFloat(0, 2*PI); var playerAngleAddAvrg = 2*PI / numPlayers; var playerAngleMaxOff = playerAngleAddAvrg/4; 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 var resourceRadius = 2/3 * mapRadius; // 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; }; //////////////// // 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) / 8192, 'max': MAX_HEIGHT * (g_Map.size + 512) / 8192, 'avg': (MIN_HEIGHT * (g_Map.size + 512) +MAX_HEIGHT * (g_Map.size + 512))/16384}; // 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, initialReliefmap); // Apply simple erosion for (var i = 0; i < 5; i++) globalSmoothHeightmap(); 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; rectangularSmoothToHeight({"x": x,"y": z} , 20, 20, playerHeight, 0.8); placeCivDefaultEntities(x, z, i+1, { '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 distributeEntitiesByHeight({ 'min': heighLimits[3], 'max': ((heighLimits[4] + heighLimits[3]) / 2) }, startLocations, 40, [templateStoneMine, templateMetalMine]); distributeEntitiesByHeight({ 'min': ((heighLimits[5] + heighLimits[6]) / 2), 'max': heighLimits[7] }, startLocations, 40, [templateStoneMine, templateMetalMine]); 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); log("Placing paths..."); var doublePaths = true; if (numPlayers > 4) doublePaths = false; 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) + if (Math.euclidDistance2D(x, z, targetX, targetZ) < pathSucsessRadius) targetReached = true; tries++; } } } RMS.SetProgress(75); 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); log("Growing fish..."); createFood ( [ [new SimpleObject(oFish, 2,3, 0,2)] ], [ 100 * numPlayers ], [avoidClasses(clFood, 5), stayClasses(clWater, 4)] ); RMS.SetProgress(85); log("Planting reeds..."); var types = [aReeds]; for (let type of types) createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(type, 1, 1, 0, 0)], true), 0, borderClasses(clWater, 0, 6), scaleByMapSize(1, 2) * 1000, 1000); RMS.SetProgress(90); log("Planting trees..."); for (var x = 0; x < mapSize; x++) { for (var z = 0;z < mapSize;z++) { - 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 + // The 0.5 is a correction for the entities placed on the center of tiles + var radius = Math.euclidDistance2D(x + 0.5, z + 0.5, mapCenterX, mapCenterZ); var minDistToSL = mapSize; for (var i=0; i < numPlayers; i++) - minDistToSL = min(minDistToSL, getDistance(playerStartLocX[i], playerStartLocZ[i], x, z)); + minDistToSL = min(minDistToSL, Math.euclidDistance2D(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 (randBool(tDensActual) && radius < playableMapRadius) { if (tDensActual < randFloat(0, bushChance * 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); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/snowflake_searocks.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/snowflake_searocks.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/snowflake_searocks.js (revision 20331) @@ -1,530 +1,530 @@ 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 tTier1Terrain = g_Terrains.tier1Terrain; const tTier2Terrain = g_Terrains.tier2Terrain; const tTier3Terrain = g_Terrains.tier3Terrain; const tHill = g_Terrains.mainTerrain; const tRoad = g_Terrains.road; const tRoadWild = g_Terrains.roadWild; const tTier4Terrain = g_Terrains.tier4Terrain; const tWater = g_Terrains.water; 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 oFruitBush = g_Gaia.fruitBush; const oMainHuntableAnimal = g_Gaia.mainHuntableAnimal; const oSecondaryHuntableAnimal = g_Gaia.secondaryHuntableAnimal; const oStoneLarge = g_Gaia.stoneLarge; const oStoneSmall = g_Gaia.stoneSmall; const oMetalLarge = g_Gaia.metalLarge; 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 pForest1 = [tForestFloor2 + TERRAIN_SEPARATOR + oTree1, tForestFloor2 + TERRAIN_SEPARATOR + oTree2, tForestFloor2]; const pForest2 = [tForestFloor1 + TERRAIN_SEPARATOR + oTree4, tForestFloor1 + TERRAIN_SEPARATOR + oTree5, tForestFloor1]; InitMap(); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); var clPlayer = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clDirt = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var clLand = createTileClass(); const playerIslandRadius = scaleByMapSize(15, 30); const islandHeight = 20; const islandBetweenPlayerAndCenterDist = 0.16; const islandBetweenPlayerAndCenterRadius = 0.81; const centralIslandRadius = 0.36; var [playerIDs, playerX, playerZ, playerAngle, startAngle] = radialPlayerPlacement(); var numIslands = 0; var isConnected = []; var islandX = []; var islandZ = []; function initIsConnected() { for (let m = 0; m < numIslands; ++m) { isConnected[m] = []; for (let n = 0; n < numIslands; ++n) isConnected[m][n] = 0; } } function createIsland(islandID, size, tileClass) { let hillSize = Math.PI * Math.pow(playerIslandRadius, 2) * size; createArea( new ClumpPlacer(hillSize, 0.95, 0.6, 10, islandX[islandID], islandZ[islandID]), [ new LayeredPainter([tCliff, tHill], [2]), new SmoothElevationPainter(ELEVATION_SET, islandHeight, 2), paintClass(tileClass) ], null); return hillSize; } function createIslandAtRadialLocation(playerID, islandID, playerIDOffset, distFromCenter, islandRadius) { let angle = startAngle + (playerID * 2 + playerIDOffset) * Math.PI / numPlayers; islandX[islandID] = Math.round(fractionToTiles(0.5 + distFromCenter * Math.cos(angle))); islandZ[islandID] = Math.round(fractionToTiles(0.5 + distFromCenter * Math.sin(angle))); createIsland(islandID, islandRadius, clLand); } function createSnowflakeSearockWithCenter(sizeID) { let [tertiaryIslandDist, tertiaryIslandRadius, islandBetweenPlayersDist, islandBetweenPlayersRadius] = islandSizes[sizeID]; let islandID_center = 4 * numPlayers; numIslands = islandID_center + 1; initIsConnected(); log("Creating central island..."); islandX[islandID_center] = fractionToTiles(0.5); islandZ[islandID_center] = fractionToTiles(0.5); createIsland(islandID_center, centralIslandRadius, clLand); for (let playerID = 0; playerID < numPlayers; ++playerID) { let playerID_neighbor = playerID + 1 < numPlayers ? playerID + 1 : 0; let islandID_player = playerID; let islandID_playerNeighbor = playerID_neighbor; let islandID_betweenPlayers = playerID + numPlayers; let islandID_betweenPlayerAndCenter = playerID + 2 * numPlayers; let islandID_betweenPlayerAndCenterNeighbor = playerID_neighbor + 2 * numPlayers; let islandID_tertiary = playerID + 3 * numPlayers; log("Creating island between the player and their neighbor..."); isConnected[islandID_betweenPlayers][islandID_player] = 1; isConnected[islandID_betweenPlayers][islandID_playerNeighbor] = 1; createIslandAtRadialLocation(playerID, islandID_betweenPlayers, 1, islandBetweenPlayersDist, islandBetweenPlayersRadius); log("Creating an island between the player and the center..."); isConnected[islandID_betweenPlayerAndCenter][islandID_player] = 1; isConnected[islandID_betweenPlayerAndCenter][islandID_center] = 1; isConnected[islandID_betweenPlayerAndCenter][islandID_betweenPlayerAndCenterNeighbor] = 1; createIslandAtRadialLocation(playerID, islandID_betweenPlayerAndCenter, 0, islandBetweenPlayerAndCenterDist, islandBetweenPlayerAndCenterRadius); log("Creating tertiary island, at the map border..."); isConnected[islandID_tertiary][islandID_betweenPlayers] = 1; createIslandAtRadialLocation(playerID, islandID_tertiary, 1, tertiaryIslandDist, tertiaryIslandRadius); } } /** * Creates one island in front of every player and connects it with the neighbors. */ function createSnowflakeSearockWithoutCenter() { numIslands = 2 * numPlayers; initIsConnected(); for (let playerID = 0; playerID < numPlayers; ++playerID) { let playerID_neighbor = playerID + 1 < numPlayers ? playerID + 1 : 0; let islandID_player = playerID; let islandID_playerNeighbor = playerID_neighbor; let islandID_inFrontOfPlayer = playerID + numPlayers; let islandID_inFrontOfPlayerNeighbor = playerID_neighbor + numPlayers; isConnected[islandID_player][islandID_playerNeighbor] = 1; isConnected[islandID_player][islandID_inFrontOfPlayer] = 1; isConnected[islandID_inFrontOfPlayer][islandID_inFrontOfPlayerNeighbor] = 1; createIslandAtRadialLocation(playerID, islandID_inFrontOfPlayer, 0, islandBetweenPlayerAndCenterDist, islandBetweenPlayerAndCenterRadius); } } function createSnowflakeSearockTiny() { numIslands = numPlayers + 1; initIsConnected(); let islandID_center = numPlayers; log("Creating central island..."); islandX[islandID_center] = fractionToTiles(0.5); islandZ[islandID_center] = fractionToTiles(0.5); createIsland(numPlayers, 1, clLand); for (let playerID = 0; playerID < numPlayers; ++playerID) { let islandID_player = playerID; isConnected[islandID_player][islandID_center] = 1; } } initTerrain(tWater); const islandSizes = { "medium": [0.41, 0.49, 0.26, 1], "large1": [0.41, 0.49, 0.24, 1], "large2": [0.41, 0.36, 0.28, 0.81] }; if (mapSize <= 128) { createSnowflakeSearockTiny(); } else if (mapSize <= 192) { createSnowflakeSearockWithoutCenter(); } else if (mapSize <= 256) { if (numPlayers < 6) createSnowflakeSearockWithCenter("medium"); else createSnowflakeSearockWithoutCenter(); } else if (mapSize <= 320) { if (numPlayers < 8) createSnowflakeSearockWithCenter("medium"); else createSnowflakeSearockWithoutCenter(); } else createSnowflakeSearockWithCenter(numPlayers < 6 ? "large1" : "large2"); for (var i = 0; i < numPlayers; ++i) { var id = playerIDs[i]; log("Creating base for player " + id + "..."); // get the x and z in tiles var fx = fractionToTiles(playerX[i]); var fz = fractionToTiles(playerZ[i]); var ix = round(fx); var iz = round(fz); islandX[i] = ix; islandZ[i] = iz; let hillSize = createIsland(i, 1, clPlayer); // create the city patch var cityRadius = playerIslandRadius/3; var placer = new ClumpPlacer(PI*cityRadius*cityRadius, 0.6, 0.3, 10, ix, iz); var painter = new LayeredPainter([tRoadWild, tRoad], [1]); createArea(placer, painter, null); placeCivDefaultEntities(fx, fz, id, { 'iberWall': 'towers' }); placeDefaultChicken(fx, fz, clBaseResource); // create berry bushes var bbAngle = randFloat(0, TWO_PI); var bbDist = 10; var bbX = round(fx + bbDist * cos(bbAngle)); var bbZ = round(fz + bbDist * sin(bbAngle)); var group = new SimpleGroup( [new SimpleObject(oFruitBush, 5,5, 0,3)], true, clBaseResource, bbX, bbZ ); createObjectGroup(group, 0); // create metal mine var mAngle = bbAngle; while(abs(mAngle - bbAngle) < PI/3) mAngle = randFloat(0, TWO_PI); var mDist = playerIslandRadius - 4; var mX = round(fx + mDist * cos(mAngle)); var mZ = round(fz + mDist * sin(mAngle)); group = new SimpleGroup( [new SimpleObject(oMetalLarge, 1,1, 0,0)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); // create stone mines mAngle += randFloat(PI/8, PI/4); mX = round(fx + mDist * cos(mAngle)); mZ = round(fz + mDist * sin(mAngle)); group = new SimpleGroup( [new SimpleObject(oStoneLarge, 1,1, 0,2)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); // create starting trees var num = floor(hillSize / 60); var tAngle = randFloat(-PI/3, 4*PI/3); var tDist = 11; var tX = round(fx + tDist * cos(tAngle)); var tZ = round(fz + tDist * sin(tAngle)); group = new SimpleGroup( [new SimpleObject(oTree1, num, num, 0,4)], false, clBaseResource, tX, tZ ); createObjectGroup(group, 0, [avoidClasses(clBaseResource,2), stayClasses(clPlayer, 3)]); placeDefaultDecoratives(fx, fz, aGrassShort, clBaseResource, playerIslandRadius); } RMS.SetProgress(30); log("Creating connectors..."); for (let ix = 0; ix < mapSize; ++ix) for (let iz = 0; iz < mapSize; ++iz) for (let m = 0; m < numIslands; ++m) for (let n = 0; n < numIslands; ++n) { if (isConnected[m][n] != 1) continue; let islandDistX = islandX[n] - islandX[m]; let islandDistZ = islandZ[m] - islandZ[n]; let d1 = islandDistX * (iz - islandZ[m]) + islandDistZ * (ix - islandX[m]); - let d2 = Math.pow(islandDistX, 2) + Math.pow(islandDistZ, 2); + let d2 = Math.square(islandDistX) + Math.square(islandDistZ); let dis = Math.abs(d1) / Math.sqrt(d2); let z = iz - islandDistX * d1 / d2; if (dis >= 5 || z < Math.min(islandZ[m], islandZ[n]) || z > Math.max(islandZ[m], islandZ[n])) continue; addToClass(ix, iz, clLand); let height; let tileClass; let f = 3 - dis; if (f > 0) { height = islandHeight; tileClass = dis < 2 ? tHill : tCliff; } else { height = islandHeight + 10 * f; tileClass = tCliff; } if (getHeight(ix, iz) < height) { placeTerrain(ix, iz, tileClass); setHeight(ix, iz, height); } } if (currentBiome() == "savanna") { var MIN_TREES = 200; var MAX_TREES = 1250; var P_FOREST = 0.02; } else if (currentBiome() == "tropic") { var MIN_TREES = 1000; var MAX_TREES = 6000; var P_FOREST = 0.6; } else { var MIN_TREES = 500; var MAX_TREES = 3000; var P_FOREST = 0.7; } var totalTrees = scaleByMapSize(MIN_TREES, MAX_TREES); var numForest = totalTrees * P_FOREST; var numStragglers = totalTrees * (1.0 - P_FOREST); log("Creating forests..."); var types = [ [[tForestFloor2, tMainTerrain, pForest1], [tForestFloor2, pForest1]], [[tForestFloor1, tMainTerrain, pForest2], [tForestFloor1, pForest2]] ]; var size = numForest / (scaleByMapSize(2, 8) * numPlayers) * (currentBiome() == "savanna" ? 2 : 1); var num = floor(size / types.length); for (let type of types) createAreas( new ClumpPlacer(numForest / num, 0.1, 0.1, 1), [ new LayeredPainter(type, [2]), paintClass(clForest) ], [avoidClasses(clPlayer, 6, clForest, 10, clHill, 0), stayClasses(clLand, 4)], num); RMS.SetProgress(55); log("Creating stone mines..."); group = new SimpleGroup([new SimpleObject(oStoneSmall, 0,2, 0,4), new SimpleObject(oStoneLarge, 1,1, 0,4)], true, clRock); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 1, clPlayer, 10, clRock, 10, clHill, 1), stayClasses(clLand, 5)], 5*scaleByMapSize(4,16), 100 ); log("Creating small stone quarries..."); group = new SimpleGroup([new SimpleObject(oStoneSmall, 2,5, 1,3)], true, clRock); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 1, clPlayer, 10, clRock, 10, clHill, 1), stayClasses(clLand, 5)], 5*scaleByMapSize(4,16), 100 ); log("Creating metal mines..."); group = new SimpleGroup([new SimpleObject(oMetalLarge, 1,1, 0,4)], true, clMetal); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 1, clPlayer, 10, clMetal, 10, clRock, 5, clHill, 1), stayClasses(clLand, 5)], 5*scaleByMapSize(4,16), 100 ); RMS.SetProgress(65); log("Creating dirt patches..."); for (let size of [scaleByMapSize(3, 48), scaleByMapSize(5, 84), scaleByMapSize(8, 128)]) createAreas( new ClumpPlacer(size, 0.3, 0.06, 0.5), [ new LayeredPainter([[tMainTerrain, tTier1Terrain],[tTier1Terrain, tTier2Terrain], [tTier2Terrain, tTier3Terrain]], [1, 1]), paintClass(clDirt) ], [avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12), stayClasses(clLand, 5)], scaleByMapSize(15, 45)); log("Creating grass patches..."); for (let size of [scaleByMapSize(2, 32), scaleByMapSize(3, 48), scaleByMapSize(5, 80)]) createAreas( new ClumpPlacer(size, 0.3, 0.06, 0.5), new TerrainPainter(tTier4Terrain), [avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12), stayClasses(clLand, 5)], scaleByMapSize(15, 45)); log("Creating small decorative rocks..."); group = new SimpleGroup( [new SimpleObject(aRockMedium, 1,3, 0,1)], true ); createObjectGroupsDeprecated( group, 0, [avoidClasses(clForest, 0, clPlayer, 0, clHill, 0), stayClasses(clLand, 4)], scaleByMapSize(16, 262), 50 ); log("Creating large decorative rocks..."); group = new SimpleGroup( [new SimpleObject(aRockLarge, 1,2, 0,1), new SimpleObject(aRockMedium, 1,3, 0,2)], true ); createObjectGroupsDeprecated( group, 0, [avoidClasses(clForest, 0, clPlayer, 0, clHill, 0), stayClasses(clLand, 4)], scaleByMapSize(8, 131), 50 ); RMS.SetProgress(70); log("Creating deer..."); group = new SimpleGroup( [new SimpleObject(oMainHuntableAnimal, 5,7, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20), stayClasses(clLand, 4)], 3 * numPlayers, 50 ); RMS.SetProgress(75); log("Creating sheep..."); group = new SimpleGroup( [new SimpleObject(oSecondaryHuntableAnimal, 2,3, 0,2)], true, clFood ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20), stayClasses(clLand, 4)], 3 * numPlayers, 50 ); log("Creating fruits..."); group = new SimpleGroup( [new SimpleObject(oFruitBush, 5,7, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clForest, 0, clPlayer, 10, clHill, 1, clFood, 20), stayClasses(clLand, 4)], 3 * numPlayers, 50 ); RMS.SetProgress(85); log("Creating straggler trees..."); var types = [oTree1, oTree2, oTree4, oTree3]; var num = floor(numStragglers / types.length); for (let type of types) createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(type, 1, 1, 0, 3)], true, clForest), 0, [avoidClasses(clForest, 1, clHill, 1, clPlayer, 9, clMetal, 6, clRock, 6), stayClasses(clLand, 4)], num); var planetm = 1; if (currentBiome() == "tropic") planetm = 8; log("Creating small grass tufts..."); group = new SimpleGroup( [new SimpleObject(aGrassShort, 1,2, 0,1, -PI/8,PI/8)] ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clHill, 2, clPlayer, 2, clDirt, 0), stayClasses(clLand, 4)], planetm * scaleByMapSize(13, 200) ); RMS.SetProgress(90); log("Creating large grass tufts..."); group = new SimpleGroup( [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)] ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clHill, 2, clPlayer, 2, clDirt, 1, clForest, 0), stayClasses(clLand, 4)], planetm * scaleByMapSize(13, 200) ); RMS.SetProgress(95); log("Creating bushes..."); group = new SimpleGroup( [new SimpleObject(aBushMedium, 1,2, 0,2), new SimpleObject(aBushSmall, 2,4, 0,2)] ); createObjectGroupsDeprecated(group, 0, [avoidClasses(clHill, 1, clPlayer, 1, clDirt, 1), stayClasses(clLand, 4)], planetm * scaleByMapSize(13, 200), 50 ); setSkySet(pickRandom(["cirrus", "cumulus", "sunny"])); setSunRotation(randFloat(0, TWO_PI)); setSunElevation(randFloat(PI/ 5, PI / 3)); ExportMap(); Index: ps/trunk/binaries/data/mods/public/maps/random/wild_lake.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/wild_lake.js (revision 20330) +++ ps/trunk/binaries/data/mods/public/maps/random/wild_lake.js (revision 20331) @@ -1,696 +1,697 @@ RMS.LoadLibrary("rmgen"); RMS.LoadLibrary("rmbiome"); RMS.LoadLibrary("heightmap"); InitMap(); let genStartTime = Date.now(); /** * getArray - To ensure a terrain texture is contained within an array */ function getArray(stringOrArrayOfStrings) { if (typeof stringOrArrayOfStrings == "string") return [stringOrArrayOfStrings]; return stringOrArrayOfStrings; } setSelectedBiome(); // Terrain, entities and actors let wildLakeBiome = [ // 0 Deep water { "texture": getArray(g_Terrains.water), "actor": [[g_Gaia.fish], 0.01], "textureHS": getArray(g_Terrains.water), "actorHS": [[g_Gaia.fish], 0.03] }, // 1 Shallow water { "texture": getArray(g_Terrains.water), "actor": [[g_Decoratives.lillies, g_Decoratives.reeds], 0.3], "textureHS": getArray(g_Terrains.water), "actorHS": [[g_Decoratives.lillies], 0.1] }, // 2 Shore { "texture": getArray(g_Terrains.shore), "actor": [ [ g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.mainHuntableAnimal, g_Decoratives.grass, g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.rockMedium, g_Decoratives.bushMedium, g_Decoratives.bushMedium ], 0.3 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] }, // 3 Low ground { "texture": getArray(g_Terrains.tier1Terrain), "actor": [ [ g_Decoratives.grass, g_Decoratives.grassShort, g_Decoratives.rockLarge, g_Decoratives.rockMedium, g_Decoratives.bushMedium, g_Decoratives.bushSmall ], 0.2 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] }, // 4 Mid ground. Player and path height { "texture": getArray(g_Terrains.mainTerrain), "actor": [ [ g_Decoratives.grass, g_Decoratives.grassShort, g_Decoratives.rockLarge, g_Decoratives.rockMedium, g_Decoratives.bushMedium, g_Decoratives.bushSmall ], 0.2 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] }, // 5 High ground { "texture": getArray(g_Terrains.tier2Terrain), "actor": [ [ g_Decoratives.grass, g_Decoratives.grassShort, g_Decoratives.rockLarge, g_Decoratives.rockMedium, g_Decoratives.bushMedium, g_Decoratives.bushSmall ], 0.2 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] }, // 6 Lower hilltop forest border { "texture": getArray(g_Terrains.dirt), "actor": [ [ g_Gaia.tree1, g_Gaia.tree3, g_Gaia.fruitBush, g_Gaia.secondaryHuntableAnimal, g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium ], 0.3 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] }, // 7 Hilltop forest { "texture": getArray(g_Terrains.forestFloor1), "actor": [ [ g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree4, g_Gaia.tree5, g_Decoratives.tree, g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium ], 0.5 ], "textureHS": getArray(g_Terrains.cliff), "actorHS": [[g_Decoratives.grassShort, g_Decoratives.rockMedium, g_Decoratives.bushSmall], 0.1] } ]; var mercenaryCampGuards = { "temperate": [ { "Template" : "structures/merc_camp_egyptian" }, { "Template" : "units/mace_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/mace_cavalry_spearman_e", "Count" : 3 }, { "Template" : "units/mace_infantry_archer_a", "Count" : 4 }, { "Template" : "units/mace_champion_infantry_a", "Count" : 3 } ], "snowy": [ { "Template" : "structures/ptol_mercenary_camp" }, { "Template" : "units/brit_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/brit_cavalry_swordsman_e", "Count" : 3 }, { "Template" : "units/brit_infantry_slinger_a", "Count" : 4 }, { "Template" : "units/brit_champion_infantry", "Count" : 3 } ], "desert": [ { "Template" : "structures/ptol_mercenary_camp" }, { "Template" : "units/pers_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/pers_cavalry_swordsman_e", "Count" : 3 }, { "Template" : "units/pers_infantry_archer_a", "Count" : 4 }, { "Template" : "units/pers_champion_infantry", "Count" : 3 } ], "alpine": [ { "Template" : "structures/ptol_mercenary_camp" }, { "Template" : "units/rome_infantry_swordsman_b", "Count" : 4 }, { "Template" : "units/rome_cavalry_spearman_e", "Count" : 3 }, { "Template" : "units/rome_infantry_javelinist_a", "Count" : 4 }, { "Template" : "units/rome_champion_infantry", "Count" : 3 } ], "mediterranean": [ { "Template" : "structures/merc_camp_egyptian" }, { "Template" : "units/iber_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/iber_cavalry_spearman_e", "Count" : 3 }, { "Template" : "units/iber_infantry_slinger_a", "Count" : 4 }, { "Template" : "units/iber_champion_infantry", "Count" : 3 } ], "savanna": [ { "Template" : "structures/merc_camp_egyptian" }, { "Template" : "units/sele_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/sele_cavalry_spearman_merc_e", "Count" : 3 }, { "Template" : "units/sele_infantry_spearman_a", "Count" : 4 }, { "Template" : "units/sele_champion_infantry_swordsman", "Count" : 3 } ], "tropic": [ { "Template" : "structures/merc_camp_egyptian" }, { "Template" : "units/ptol_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/ptol_cavalry_archer_e", "Count" : 3 }, { "Template" : "units/ptol_infantry_slinger_a", "Count" : 4 }, { "Template" : "units/ptol_champion_infantry_pikeman", "Count" : 3 } ], "autumn": [ { "Template" : "structures/ptol_mercenary_camp" }, { "Template" : "units/gaul_infantry_javelinist_b", "Count" : 4 }, { "Template" : "units/gaul_cavalry_swordsman_e", "Count" : 3 }, { "Template" : "units/gaul_infantry_slinger_a", "Count" : 4 }, { "Template" : "units/gaul_champion_infantry", "Count" : 3 } ] }; /** * Resource spots and other points of interest */ // Mines function placeMine(point, centerEntity, decorativeActors = [ g_Decoratives.grass, g_Decoratives.grassShort, g_Decoratives.rockLarge, g_Decoratives.rockMedium, g_Decoratives.bushMedium, g_Decoratives.bushSmall ] ) { placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI)); let quantity = randIntInclusive(11, 23); let dAngle = TWO_PI / quantity; for (let i = 0; i < quantity; ++i) { let angle = dAngle * randFloat(i, i + 1); let dist = randFloat(2, 5); placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(decorativeActors), 0, randFloat(0, 2 * PI)); } } // Groves, only Wood let groveActors = [g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium]; let clGrove = createTileClass(); function placeGrove(point, groveEntities = [ g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree4, g_Gaia.tree4, g_Gaia.tree5 ], groveActors = [g_Decoratives.grass, g_Decoratives.rockMedium, g_Decoratives.bushMedium], groveTileClass = undefined, groveTerrainTexture = getArray(g_Terrains.forestFloor1) ) { placeObject(point.x, point.y, pickRandom(["structures/gaul_outpost", "gaia/flora_tree_oak_new"]), 0, randFloat(0, 2 * PI)); let quantity = randIntInclusive(20, 30); let dAngle = TWO_PI / quantity; for (let i = 0; i < quantity; ++i) { let angle = dAngle * randFloat(i, i + 1); let dist = randFloat(2, 5); let objectList = groveEntities; if (i % 3 == 0) objectList = groveActors; let x = point.x + dist * Math.cos(angle); let y = point.y + dist * Math.sin(angle); placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * PI)); if (groveTileClass) createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter(groveTerrainTexture), paintClass(groveTileClass)]); else createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter(groveTerrainTexture)]); } } var farmEntities = { "temperate": { "building": "structures/mace_farmstead", "animal": "gaia/fauna_pig" }, "snowy": { "building": "structures/brit_farmstead", "animal": "gaia/fauna_sheep" }, "desert": { "building": "structures/pers_farmstead", "animal": "gaia/fauna_camel" }, "alpine": { "building": "structures/rome_farmstead", "animal": "gaia/fauna_sheep" }, "mediterranean": { "building": "structures/iber_farmstead", "animal": "gaia/fauna_pig" }, "savanna": { "building": "structures/sele_farmstead", "animal": "gaia/fauna_horse" }, "tropic": { "building": "structures/ptol_farmstead", "animal": "gaia/fauna_camel" }, "autumn": { "building": "structures/gaul_farmstead", "animal": "gaia/fauna_horse" } }; wallStyles.other.sheepIn = new WallElement("sheepIn", farmEntities[currentBiome()].animal, PI / 4, -1.5, 0.75, PI/2); wallStyles.other.foodBin = new WallElement("foodBin", "gaia/special_treasure_food_bin", PI/2, 1.5); wallStyles.other.sheep = new WallElement("sheep", farmEntities[currentBiome()].animal, 0, 0, 0.75); wallStyles.other.farm = new WallElement("farm", farmEntities[currentBiome()].building, PI, 0, -3); let fences = [ new Fortress("fence", ["foodBin", "farm", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence"]), new Fortress("fence", ["foodBin", "farm", "fence", "sheepIn", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence"]), new Fortress("fence", [ "foodBin", "farm", "cornerIn", "bench", "cornerOut", "fence_short", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence_short", "sheep", "fence" ]), new Fortress("fence", [ "foodBin", "farm", "cornerIn", "fence_short", "cornerOut", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence_short", "sheep", "fence" ]), new Fortress("fence", [ "foodBin", "farm", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence" ]) ]; let num = fences.length; for (let i = 0; i < num; ++i) fences.push(new Fortress("fence", clone(fences[i].wall).reverse())); // Camps with fire and gold treasure function placeCamp(point, centerEntity = "actor|props/special/eyecandy/campfire.xml", otherEntities = ["gaia/special_treasure_metal", "gaia/special_treasure_standing_stone", "units/brit_infantry_slinger_b", "units/brit_infantry_javelinist_b", "units/gaul_infantry_slinger_b", "units/gaul_infantry_javelinist_b", "units/gaul_champion_fanatic", "actor|props/special/common/waypoint_flag.xml", "actor|props/special/eyecandy/barrel_a.xml", "actor|props/special/eyecandy/basket_celt_a.xml", "actor|props/special/eyecandy/crate_a.xml", "actor|props/special/eyecandy/dummy_a.xml", "actor|props/special/eyecandy/handcart_1.xml", "actor|props/special/eyecandy/handcart_1_broken.xml", "actor|props/special/eyecandy/sack_1.xml", "actor|props/special/eyecandy/sack_1_rough.xml" ] ) { placeObject(point.x, point.y, centerEntity, 0, randFloat(0, TWO_PI)); let quantity = randIntInclusive(5, 11); let dAngle = TWO_PI / quantity; for (let i = 0; i < quantity; ++i) { let angle = dAngle * randFloat(i, i + 1); let dist = randFloat(1, 3); placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(otherEntities), 0, randFloat(0, 2 * PI)); } } function placeStartLocationResources( point, foodEntities = [g_Gaia.fruitBush, g_Gaia.chicken], groveEntities = [ g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree1, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree2, g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree3, g_Gaia.tree4, g_Gaia.tree4, g_Gaia.tree5 ], groveTerrainTexture = getArray(g_Terrains.forestFloor1), averageDistToCC = 10, dAverageDistToCC = 2 ) { function getRandDist() { return averageDistToCC + randFloat(-dAverageDistToCC, dAverageDistToCC); } let currentAngle = randFloat(0, TWO_PI); // Stone let dAngle = TWO_PI * 2 / 9; let angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4); placeMine({ "x": point.x + averageDistToCC * Math.cos(angle), "y": point.y + averageDistToCC * Math.sin(angle) }, g_Gaia.stoneLarge); currentAngle += dAngle; // Wood let quantity = 80; dAngle = TWO_PI / quantity / 3; for (let i = 0; i < quantity; ++i) { angle = currentAngle + randFloat(0, dAngle); let dist = getRandDist(); let objectList = groveEntities; if (i % 2 == 0) objectList = groveActors; let x = point.x + dist * Math.cos(angle); let y = point.y + dist * Math.sin(angle); placeObject(x, y, pickRandom(objectList), 0, randFloat(0, 2 * PI)); createArea(new ClumpPlacer(5, 1, 1, 1, floor(x), floor(y)), [new TerrainPainter(groveTerrainTexture), paintClass(clGrove)]); currentAngle += dAngle; } // Metal dAngle = TWO_PI * 2 / 9; angle = currentAngle + randFloat(dAngle / 4, 3 * dAngle / 4); placeMine({ "x": point.x + averageDistToCC * Math.cos(angle), "y": point.y + averageDistToCC * Math.sin(angle) }, g_Gaia.metalLarge); currentAngle += dAngle; // Berries and domestic animals quantity = 15; dAngle = TWO_PI / quantity * 2 / 9; for (let i = 0; i < quantity; ++i) { angle = currentAngle + randFloat(0, dAngle); let dist = getRandDist(); placeObject(point.x + dist * Math.cos(angle), point.y + dist * Math.sin(angle), pickRandom(foodEntities), 0, randFloat(0, 2 * PI)); currentAngle += dAngle; } } log("Functions loaded after " + ((Date.now() - genStartTime) / 1000) + "s"); /** * Base terrain shape generation and settings */ // Height range by map size let heightScale = (g_Map.size + 256) / 768 / 4; let heightRange = { "min": MIN_HEIGHT * heightScale, "max": MAX_HEIGHT * heightScale }; // Water coverage let averageWaterCoverage = 1/5; // NOTE: Since terrain generation is quite unpredictable actual water coverage might vary much with the same value let waterHeight = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); // Water height in environment and the engine let waterHeightAdjusted = waterHeight + MIN_HEIGHT; // Water height as terrain height setWaterHeight(waterHeight); // Generate base terrain shape let lowH = heightRange.min; let medH = (heightRange.min + heightRange.max) / 2; // Lake let initialHeightmap = [ [medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH], [medH, medH, lowH, lowH, medH, medH], [medH, medH, lowH, lowH, medH, medH], [medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH], ]; if (g_Map.size < 256) { initialHeightmap = [ [medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH], [medH, medH, lowH, medH, medH], [medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH] ]; } if (g_Map.size >= 384) { initialHeightmap = [ [medH, medH, medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH, medH, medH], [medH, medH, medH, lowH, lowH, medH, medH, medH], [medH, medH, medH, lowH, lowH, medH, medH, medH], [medH, medH, medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH, medH, medH], [medH, medH, medH, medH, medH, medH, medH, medH], ]; } setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8); // Apply simple erosion for (let i = 0; i < 5; ++i) splashErodeMap(0.1); globalSmoothHeightmap(); // Final rescale rescaleHeightmap(heightRange.min, heightRange.max); RMS.SetProgress(25); /** * Prepare terrain texture placement */ let heighLimits = [ heightRange.min + 3/4 * (waterHeightAdjusted - heightRange.min), // 0 Deep water waterHeightAdjusted, // 1 Shallow water waterHeightAdjusted + 2/8 * (heightRange.max - waterHeightAdjusted), // 2 Shore waterHeightAdjusted + 3/8 * (heightRange.max - waterHeightAdjusted), // 3 Low ground waterHeightAdjusted + 4/8 * (heightRange.max - waterHeightAdjusted), // 4 Player and path height waterHeightAdjusted + 6/8 * (heightRange.max - waterHeightAdjusted), // 5 High ground waterHeightAdjusted + 7/8 * (heightRange.max - waterHeightAdjusted), // 6 Lower forest border heightRange.max // 7 Forest ]; let playerHeightRange = { "min" : heighLimits[3], "max" : heighLimits[4] }; let resourceSpotHeightRange = { "min" : (heighLimits[2] + heighLimits[3]) / 2, "max" : (heighLimits[4] + heighLimits[5]) / 2 }; let playerHeight = (playerHeightRange.min + playerHeightRange.max) / 2; // Average player height log("Terrain shape generation and biome presets after " + ((Date.now() - genStartTime) / 1000) + "s"); /** * Get start locations */ let startLocations = getStartLocationsByHeightmap(playerHeightRange, 1000, 30); // Sort start locations to form a "ring" let startLocationOrder = getOrderOfPointsForShortestClosePath(startLocations); let newStartLocations = []; for (let i = 0; i < startLocations.length; ++i) newStartLocations.push(startLocations[startLocationOrder[i]]); startLocations = newStartLocations; // Sort players by team let playerIDs = []; let teams = []; for (let i = 0; i < g_MapSettings.PlayerData.length - 1; ++i) { playerIDs.push(i+1); let t = g_MapSettings.PlayerData[i + 1].Team; if (teams.indexOf(t) == -1 && t !== undefined) teams.push(t); } playerIDs = sortPlayers(playerIDs); // Minimize maximum distance between players within a team if (teams.length) { let minDistance = Infinity; let bestShift; for (let s = 0; s < playerIDs.length; ++s) { let maxTeamDist = 0; for (let pi = 0; pi < playerIDs.length - 1; ++pi) { let p1 = playerIDs[(pi + s) % playerIDs.length] - 1; let t1 = getPlayerTeam(p1); if (teams.indexOf(t1) === -1) continue; for (let pj = pi + 1; pj < playerIDs.length; ++pj) { let p2 = playerIDs[(pj + s) % playerIDs.length] - 1; let t2 = getPlayerTeam(p2); if (t2 != t1) continue; - let l1 = startLocations[pi]; - let l2 = startLocations[pj]; - let dist = getDistance(l1.x, l1.y, l2.x, l2.y); - maxTeamDist = Math.max(dist, maxTeamDist); + maxTeamDist = Math.max( + maxTeamDist, + Math.euclidDistance2D( + startLocations[pi].x, startLocations[pi].y, + startLocations[pj].x, startLocations[pj].y)); } } if (maxTeamDist < minDistance) { minDistance = maxTeamDist; bestShift = s; } } if (bestShift) { let newPlayerIDs = []; for (let i = 0; i < playerIDs.length; ++i) newPlayerIDs.push(playerIDs[(i + bestShift) % playerIDs.length]); playerIDs = newPlayerIDs; } } log("Start location chosen after " + ((Date.now() - genStartTime) / 1000) + "s"); RMS.SetProgress(30); /** * Smooth Start Locations before height region calculation */ let playerBaseRadius = 35; if (g_Map.size < 256) playerBaseRadius = 25; for (let p = 0; p < playerIDs.length; ++p) rectangularSmoothToHeight(startLocations[p], playerBaseRadius, playerBaseRadius, playerHeight, 0.7); /** * Calculate tile centered height map after start position smoothing but before placing paths * This has nothing to to with TILE_CENTERED_HEIGHT_MAP which should be false! */ let tchm = getTileCenteredHeightmap(); /** * Add paths (If any) */ let clPath = createTileClass(); /** * Divide tiles in areas by height and avoid paths */ let areas = []; for (let h = 0; h < heighLimits.length; ++h) areas.push([]); for (let x = 0; x < tchm.length; ++x) { for (let y = 0; y < tchm[0].length; ++y) { if (g_Map.tileClasses[clPath].inclusionCount[x][y] > 0) // Avoid paths continue; let minHeight = heightRange.min; for (let h = 0; h < heighLimits.length; ++h) { if (tchm[x][y] >= minHeight && tchm[x][y] <= heighLimits[h]) { areas[h].push({ "x": x, "y": y }); break; } else minHeight = heighLimits[h]; } } } /** * Get max slope of each area */ let slopeMap = getSlopeMap(); let minSlope = []; let maxSlope = []; for (let h = 0; h < heighLimits.length; ++h) { minSlope[h] = Infinity; maxSlope[h] = 0; for (let t = 0; t < areas[h].length; ++t) { let x = areas[h][t].x; let y = areas[h][t].y; let slope = slopeMap[x][y]; if (slope > maxSlope[h]) maxSlope[h] = slope; if (slope < minSlope[h]) minSlope[h] = slope; } } /** * Paint areas by height and slope */ for (let h = 0; h < heighLimits.length; ++h) { for (let t = 0; t < areas[h].length; ++t) { let x = areas[h][t].x; let y = areas[h][t].y; let actor; let texture = pickRandom(wildLakeBiome[h].texture); if (slopeMap[x][y] < 0.5 * (minSlope[h] + maxSlope[h])) { if (randBool(wildLakeBiome[h].actor[1])) actor = pickRandom(wildLakeBiome[h].actor[0]); } else { texture = pickRandom(wildLakeBiome[h].textureHS); if (randBool(wildLakeBiome[h].actorHS[1])) actor = pickRandom(wildLakeBiome[h].actorHS[0]); } g_Map.texture[x][y] = g_Map.getTextureID(texture); if (actor) placeObject(randFloat(x, x + 1), randFloat(y, y + 1), actor, 0, randFloat(0, 2 * PI)); } } log("Terrain texture placement finished after " + ((Date.now() - genStartTime) / 1000) + "s"); RMS.SetProgress(80); /** * Get resource spots after players start locations calculation and paths */ let avoidPoints = clone(startLocations); for (let i = 0; i < avoidPoints.length; ++i) avoidPoints[i].dist = 30; let resourceSpots = getPointsByHeight(resourceSpotHeightRange, avoidPoints, clPath); log("Resource spots chosen after " + ((Date.now() - genStartTime) / 1000) + "s"); RMS.SetProgress(55); /** * Add start locations and resource spots after terrain texture and path painting */ for (let p = 0; p < playerIDs.length; ++p) { let point = startLocations[p]; placeCivDefaultEntities(point.x, point.y, playerIDs[p], { "iberWall": g_Map.size > 192 }); placeStartLocationResources(point); } let mercenaryCamps = ceil(g_Map.size / 256); log("Maximum number of mercenary camps: " + uneval(mercenaryCamps)); for (let i = 0; i < resourceSpots.length; ++i) { let choice = i % 5; if (choice == 0) placeMine(resourceSpots[i], g_Gaia.stoneLarge); if (choice == 1) placeMine(resourceSpots[i], g_Gaia.metalLarge); if (choice == 2) placeGrove(resourceSpots[i]); if (choice == 3) { placeCamp(resourceSpots[i]); rectangularSmoothToHeight(resourceSpots[i], 5, 5, g_Map.height[resourceSpots[i].x][resourceSpots[i].y] - 10, 0.5); } if (choice == 4) { if (mercenaryCamps) { createStartingPlayerEntities(resourceSpots[i].x, resourceSpots[i].y, 0, mercenaryCampGuards[currentBiome()]); rectangularSmoothToHeight(resourceSpots[i], 15, 15, g_Map.height[resourceSpots[i].x][resourceSpots[i].y], 0.5); --mercenaryCamps; } else { placeCustomFortress(resourceSpots[i].x, resourceSpots[i].y, pickRandom(fences), "other", 0, randFloat(0, 2 * PI)); rectangularSmoothToHeight(resourceSpots[i], 10, 10, g_Map.height[resourceSpots[i].x][resourceSpots[i].y], 0.5); } } } log("Map generation finished after " + ((Date.now() - genStartTime) / 1000) + "s"); ExportMap();