Index: ps/trunk/binaries/data/mods/public/maps/random/corsica.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/corsica.js (revision 20731) +++ ps/trunk/binaries/data/mods/public/maps/random/corsica.js (revision 20732) @@ -1,680 +1,575 @@ Engine.LoadLibrary("rmgen"); var tGrass = ["medit_grass_field", "medit_grass_field_b", "temp_grass_c"]; var tLushGrass = ["medit_grass_field","medit_grass_field_a"]; var tSteepCliffs = ["temp_cliff_b", "temp_cliff_a"]; var tCliffs = ["temp_cliff_b", "medit_cliff_italia", "medit_cliff_italia_grass"]; var tHill = ["medit_cliff_italia_grass","medit_cliff_italia_grass", "medit_grass_field", "medit_grass_field", "temp_grass"]; var tMountain = ["medit_cliff_italia_grass","medit_cliff_italia"]; var tRoad = ["medit_city_tile","medit_rocks_grass","medit_grass_field_b"]; var tRoadWild = ["medit_rocks_grass","medit_grass_field_b"]; var tShoreBlend = ["medit_sand_wet","medit_rocks_wet"]; var tShore = ["medit_rocks","medit_sand","medit_sand"]; var tSandTransition = ["medit_sand","medit_rocks_grass","medit_rocks_grass","medit_rocks_grass"]; var tVeryDeepWater = ["medit_sea_depths","medit_sea_coral_deep"]; var tDeepWater = ["medit_sea_coral_deep","tropic_ocean_coral"]; var tCreekWater = "medit_sea_coral_plants"; var ePine = "gaia/flora_tree_aleppo_pine"; var ePalmTall = "gaia/flora_tree_cretan_date_palm_tall"; var eFanPalm = "gaia/flora_tree_medit_fan_palm"; var eApple = "gaia/flora_tree_apple"; var eBush = "gaia/flora_bush_berry"; var eFish = "gaia/fauna_fish"; var ePig = "gaia/fauna_pig"; var eStoneMine = "gaia/geology_stonemine_medit_quarry"; var eMetalMine = "gaia/geology_metal_mediterranean_slabs"; var aRock = "actor|geology/stone_granite_med.xml"; var aLargeRock = "actor|geology/stone_granite_large.xml"; var aBushA = "actor|props/flora/bush_medit_sm_lush.xml"; var aBushB = "actor|props/flora/bush_medit_me_lush.xml"; var aPlantA = "actor|props/flora/plant_medit_artichoke.xml"; var aPlantB = "actor|props/flora/grass_tufts_a.xml"; var aPlantC = "actor|props/flora/grass_soft_tuft_a.xml"; var aStandingStone = "actor|props/special/eyecandy/standing_stones.xml"; InitMap(); var numPlayers = getNumPlayers(); var mapSize = getMapSize(); +var mapCenter = getMapCenter(); var clIsland = createTileClass(); var clCreek = createTileClass(); var clWater = createTileClass(); var clCliffs = createTileClass(); var clForest = createTileClass(); var clShore = createTileClass(); var clPlayer = createTileClass(); var clBaseResource = createTileClass(); var clPassage = createTileClass(); var clWater = createTileClass(); var clSettlement = createTileClass(); initTerrain(tVeryDeepWater); +var radiusBeach = fractionToTiles(0.57); +var radiusCreeks = fractionToTiles(0.52); +var radiusIsland = fractionToTiles(0.4); +var radiusLevel1 = fractionToTiles(0.35); +var radiusPlayer = fractionToTiles(0.25); +var radiusLevel2 = fractionToTiles(0.2); + +var creeksArea = () => randBool() ? randFloat(10, 50) : scaleByMapSize(75, 100) + randFloat(0, 20); + var nbCreeks = scaleByMapSize(6, 15); var nbSubIsland = 5; var nbBeaches = scaleByMapSize(2, 5); -var nbPassagesIsland = scaleByMapSize(1, 4); - -var beachSmallRadius = fractionToTiles(0.45); -var beachBigRadius = fractionToTiles(0.57); +var nbPassagesLevel1 = scaleByMapSize(4, 8); +var nbPassagesLevel2 = scaleByMapSize(2, 4); var heightMain = 5; var heightCreeks = -5; var heightBeaches = -1; var heightOffsetMainRelief = 30; var heightOffsetLevel1 = 9; var heightOffsetLevel2 = 8; var heightOffsetBumps = 2; var heightOffsetAntiBumps = -5; -log("Creating Corsica and Sardinia"); -var islandX = [0.01, 0.99]; -var islandZ = [0.1, 0.9]; - +log("Creating Corsica and Sardinia..."); var swapAngle = randBool() ? Math.PI / 2 : 0; -if (swapAngle) - islandX.reverse(); +var islandLocations = [new Vector2D(0.05, 0.05), new Vector2D(0.95, 0.95)].map(v => v.mult(mapSize).rotateAround(-swapAngle, mapCenter)); for (let island = 0; island < 2; ++island) { - let fx = fractionToTiles(islandX[island]); - let fz = fractionToTiles(islandZ[island]); - log("Creating island area..."); createArea( - new ClumpPlacer(fractionToSize(0.3) * 1.8, 1, 0.5, 10, Math.round(fx), Math.round(fz)), + new ClumpPlacer(diskArea(radiusIsland), 1, 0.5, 10, islandLocations[island].x, islandLocations[island].y), [ new LayeredPainter([tCliffs, tGrass], [2]), new SmoothElevationPainter(ELEVATION_SET, heightMain, 0), paintClass(clIsland) - ], - null); + ]); log("Creating subislands..."); for (let i = 0; i < nbSubIsland + 1; ++i) { - let angle = Math.PI * (island - i / (nbSubIsland * 2)); - if (!swapAngle) - angle *= -1; - + let angle = Math.PI * (island + i / (nbSubIsland * 2)) + swapAngle; + let location = Vector2D.add(islandLocations[island], new Vector2D(radiusIsland, 0).rotate(-angle)); createArea( - new ClumpPlacer( - fractionToSize(0.05) / 2, - 0.6, - 0.03, - 10, - Math.round(fx + Math.sqrt(fractionToSize(0.3) * 0.55) * Math.sin(angle)), - Math.round(fz + Math.sqrt(fractionToSize(0.3) * 0.55) * Math.cos(angle))), + new ClumpPlacer(fractionToSize(0.05) / 2, 0.6, 0.03, 10, location.x, location.y), [ new LayeredPainter([tCliffs, tGrass], [2]), new SmoothElevationPainter(ELEVATION_SET, heightMain, 1), paintClass(clIsland) - ], - null); + ]); } - log("Creating creeks"); + log("Creating creeks..."); for (let i = 0; i < nbCreeks + 1; ++i) { - let radius = fractionToTiles(randFloat(0.49, 0.55)); let angle = Math.PI * (island + i * (1 / (nbCreeks * 2))) + swapAngle; - + let location = Vector2D.add(islandLocations[island], new Vector2D(radiusCreeks, 0).rotate(-angle)); createArea( - new ClumpPlacer( - randBool() ? randFloat(10, 50) : scaleByMapSize(75, 100) + randFloat(0, 20), - 0.4, - 0.01, - 10, - Math.round(fx + radius * Math.cos(angle)), - Math.round(fz + radius * Math.sin(angle))), + new ClumpPlacer(creeksArea(), 0.4, 0.01, 10, location.x, location.y), [ new TerrainPainter(tSteepCliffs), new SmoothElevationPainter(ELEVATION_SET, heightCreeks, 0), paintClass(clCreek) - ], - null); + ]); } log("Creating beaches..."); for (let i = 0; i < nbBeaches + 1; ++i) { let angle = Math.PI * (island + (i / (nbBeaches * 2.5)) + 1 / (nbBeaches * 6) + randFloat(-1, 1) / (nbBeaches * 7)) + swapAngle; - let startX = Math.round(fx + beachSmallRadius * Math.cos(angle)); - let startZ = Math.round(fz + beachSmallRadius * Math.sin(angle)); - - let endX = Math.round(fx + beachBigRadius * Math.cos(angle)); - let endZ = Math.round(fz + beachBigRadius * Math.sin(angle)); + let start = Vector2D.add(islandLocations[island], new Vector2D(radiusIsland, 0).rotate(-angle)); + let end = Vector2D.add(islandLocations[island], new Vector2D(radiusBeach, 0).rotate(-angle)); createArea( - new ClumpPlacer(130, 0.7, 0.8, 10, Math.round((startX + endX * 3) / 4), Math.round((startZ + endZ * 3) / 4)), - [new SmoothElevationPainter(ELEVATION_SET, heightBeaches, 5)], - null); - - straightPassageMaker( - Math.max(0, Math.min(startX, mapSize)), - Math.max(0, Math.min(startZ, mapSize)), - Math.max(0, Math.min(endX, mapSize)), - Math.max(0, Math.min(endZ, mapSize)), - 25, - 18, - 4, - clShore, - null); - } + new ClumpPlacer(130, 0.7, 0.8, 10, Math.round((start.x + end.x * 3) / 4), Math.round((start.y + end.y * 3) / 4)), + new SmoothElevationPainter(ELEVATION_SET, heightBeaches, 5)); - let x = Math.round((fx * 5 + fractionToTiles(0.5)) / 6.0); - let z = Math.round(fz); + createPassage({ + "start": start, + "end": end, + "startWidth": 18, + "endWidth": 25, + "smoothWidth": 4, + "tileClass": clShore + }); + } - log("Creating main relief"); + log("Creating main relief..."); createArea( - new ClumpPlacer(fractionToSize(0.3) * 1.8, 1, 0.2, 4, x, z), - new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetMainRelief, fractionToTiles(0.45)), - null); + new ClumpPlacer(diskArea(radiusIsland), 1, 0.2, 10, islandLocations[island].x, islandLocations[island].y), + new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetMainRelief, fractionToTiles(0.45))); - log("Creating first level plateau"); + log("Creating first level plateau..."); createArea( - new ClumpPlacer(fractionToSize(0.18) * 1.8, 0.95, 0.02, 4, x, z), - new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetLevel1, 1), - null); + new ClumpPlacer(diskArea(radiusLevel1), 0.95, 0.02, 10, islandLocations[island].x, islandLocations[island].y), + new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetLevel1, 1)); log("Creating first level passages..."); - for (let i = 0; i <= 3; ++i) + for (let i = 0; i <= nbPassagesLevel1; ++i) { - let radius = Math.sqrt(fractionToSize(0.18) * 1.8 / Math.PI) + 2; let angle = Math.PI * (i / 7 + 1 / 9 + island) + swapAngle; - - straightPassageMaker( - Math.round(x + (radius + 7) * Math.cos(angle)), - Math.round(z + (radius + 7) * Math.sin(angle)), - Math.round(x + (radius - 5) * Math.cos(angle)), - Math.round(z + (radius - 5) * Math.sin(angle)), - 4, - 10, - 3, - clPassage, - tGrass); + createPassage({ + "start": Vector2D.add(islandLocations[island], new Vector2D(radiusLevel1 + 10, 0).rotate(-angle)), + "end": Vector2D.add(islandLocations[island], new Vector2D(radiusLevel1 - 4, 0).rotate(-angle)), + "startWidth": 10, + "endWidth": 6, + "smoothWidth": 3, + "tileClass": clPassage + }); } if (mapSize > 150) { - log("Creating second level plateau"); + log("Creating second level plateau..."); createArea( - new ClumpPlacer(fractionToSize(0.1), 0.98, 0.04, 4, x, z), + new ClumpPlacer(diskArea(radiusLevel2), 0.98, 0.04, 10, islandLocations[island].x, islandLocations[island].y), [ new LayeredPainter([tCliffs, tGrass], [2]), new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetLevel2, 1) - ], - null); + ]); log("Creating second level passages..."); - for (let i = 0; i < nbPassagesIsland; ++i) + for (let i = 0; i < nbPassagesLevel2; ++i) { - let radius = Math.sqrt(fractionToSize(0.1) / Math.PI) + 2; - let angle = Math.PI * (i / (2 * nbPassagesIsland) + 1 / (4 * nbPassagesIsland) + island) + swapAngle; - - straightPassageMaker( - Math.round(x + (radius + 5) * Math.cos(angle)), - Math.round(z + (radius + 5) * Math.sin(angle)), - Math.round(x + (radius - 4) * Math.cos(angle)), - Math.round(z + (radius - 4) * Math.sin(angle)), - 1, - 6, - 2, - clPassage, - tGrass); + let angle = Math.PI * (i / (2 * nbPassagesLevel2) + 1 / (4 * nbPassagesLevel2) + island) + swapAngle; + createPassage({ + "start": Vector2D.add(islandLocations[island], new Vector2D(radiusLevel2 + 3, 0).rotate(-angle)), + "end": Vector2D.add(islandLocations[island], new Vector2D(radiusLevel2 - 6, 0).rotate(-angle)), + "startWidth": 4, + "endWidth": 6, + "smoothWidth": 2, + "tileClass": clPassage + }); } } } Engine.SetProgress(30); log("Determining player locations..."); var playerIDs = sortAllPlayers(); var playerX = []; var playerZ = []; var playerAngle = []; var p = 0; for (let island = 0; island < 2; ++island) { let playersPerIsland = island == 0 ? Math.ceil(numPlayers / 2) : Math.floor(numPlayers / 2); for (let i = 0; i < playersPerIsland; ++i) { - let angle = Math.PI * ((i + 0.5) / (2 * playersPerIsland) + island) + swapAngle; - playerAngle[p] = angle; - playerX[p] = islandX[island] + 0.36 * Math.cos(angle); - playerZ[p] = island + 0.36 * Math.sin(angle); + playerAngle[p] = Math.PI * ((i + 0.5) / (2 * playersPerIsland) + island) + swapAngle; + let pos = Vector2D.add(islandLocations[island], new Vector2D(radiusPlayer).rotate(-playerAngle[p])); + [playerX[p], playerZ[p]] = [pos.x, pos.y]; ++p; } } for (var i = 0; i < numPlayers; i++) { var id = playerIDs[i]; log("Creating base for player " + id + "..."); var radius = 23; // get the x and z in tiles - let fx = fractionToTiles(playerX[i]); - let fz = fractionToTiles(playerZ[i]); + let fx = playerX[i]; + let fz = playerZ[i]; // let's create a nice platform var placer = new ClumpPlacer(PI*radius*radius, 0.95, 0.3, 10, fx,fz); var PlayerArea = createArea(placer, [paintClass(clPlayer)], null); // create the city patch var cityRadius = radius/4; placer = new ClumpPlacer(PI*cityRadius*cityRadius, 0.8, 0.3, 10, fx, fz); var painter = new LayeredPainter([tRoadWild,tRoad],[1]); var elevationPainter = new SmoothElevationPainter(ELEVATION_SET, getHeight(Math.round(fx), Math.round(fz)),10); createArea(placer, [painter,paintClass(clSettlement),elevationPainter], null); placeCivDefaultEntities(fx, fz, id, { 'iberWall': false }); placeDefaultChicken(fx, fz, clBaseResource); // create berry bushes var bbAngle = randFloat(0, 2 * PI); var bbDist = 11; var bbX = round(fx + bbDist * cos(bbAngle)); var bbZ = round(fz + bbDist * sin(bbAngle)); var group = new SimpleGroup( [new SimpleObject(eBush, 5,5, 1,2)], true, clBaseResource, bbX, bbZ ); createObjectGroup(group, 0); // create metal mine // this makes sure it's created on the same level as the player. var mAngle = randFloat(playerAngle[i] + PI/2,playerAngle[i] + PI/3); var mDist = 18; 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(ePine, 0,1, 3,3)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); // create stone mines mAngle += randFloat(PI/8, PI/5); 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(ePine, 0,1, 3,3)], true, clBaseResource, mX, mZ ); createObjectGroup(group, 0); group = new SimpleGroup([new SimpleObject(ePine, 1,3, 1,4),new SimpleObject(ePalmTall, 0,1, 1,4),new SimpleObject(eFanPalm, 0,1, 0,2)], true, clForest); createObjectGroupsDeprecated(group, 0, [avoidClasses(clBaseResource,3, clSettlement,0), stayClasses(clPlayer,1)], 150, 1000); } - Engine.SetProgress(40); -log("Creating bumps"); +log("Creating bumps..."); createAreas( new ClumpPlacer(70, 0.6, 0.1, 4), [new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetBumps, 3)], [ stayClasses(clIsland, 2), avoidClasses(clPlayer, 6, clPassage, 2) ], scaleByMapSize(20, 100), 5); -log("Creating anti bumps"); +log("Creating anti bumps..."); createAreas( new ClumpPlacer(120, 0.3, 0.1, 4), - [new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetAntiBumps, 6)], + new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetAntiBumps, 6), avoidClasses(clPlayer, 6, clPassage, 2, clIsland, 2), scaleByMapSize(20, 100), 5); log("Painting water..."); for (let mapX = 0; mapX < mapSize; ++mapX) for (let mapZ = 0; mapZ < mapSize; ++mapZ) if (getHeight(mapX, mapZ) < 0) addToClass(mapX, mapZ, clWater); log("Painting land..."); for (let mapX = 0; mapX < mapSize; ++mapX) for (let mapZ = 0; mapZ < mapSize; ++mapZ) { let terrain = getCosricaSardiniaTerrain(mapX, mapZ); if (!terrain) continue; createTerrain(terrain).place(mapX, mapZ); if (terrain == tCliffs || terrain == tSteepCliffs) addToClass(mapX, mapZ, clCliffs); } function getCosricaSardiniaTerrain(mapX, mapZ) { let isWater = getTileClass(clWater).countMembersInRadius(mapX, mapZ, 3); let isShore = getTileClass(clShore).countMembersInRadius(mapX, mapZ, 2); let isPassage = getTileClass(clPassage).countMembersInRadius(mapX, mapZ, 2); let isSettlement = getTileClass(clSettlement).countMembersInRadius(mapX, mapZ, 2); if (isSettlement) return undefined; let height = getHeight(mapX, mapZ); let heightDiff = getHeightDiff(mapX, mapZ); if (height >= 0.5 && height < 1.5 && isShore) return tSandTransition; // Paint land cliffs and grass if (height >= 1 && !isWater) { if (isPassage) return tGrass; if (heightDiff >= 10) return height > 25 ? tSteepCliffs : tCliffs; if (height < 17) return tGrass; if (heightDiff < 5) return tHill; return tMountain; } if (heightDiff >= 9) return tCliffs; if (height >= 1.5) return undefined; if (height >= -0.75) return tShore; if (height >= -3) return tShoreBlend; if (height >= -6) return tCreekWater; if (height > -10 && heightDiff < 6) return tDeepWater; return undefined; } Engine.SetProgress(65); log("Creating mines..."); for (let mine of [eMetalMine, eStoneMine]) createObjectGroupsDeprecated( new SimpleGroup( [ new SimpleObject(mine, 1,1, 0,0), new SimpleObject(aBushB, 1,1, 2,2), new SimpleObject(aBushA, 0,2, 1,3) ], true, clBaseResource), 0, [ stayClasses(clIsland, 1), avoidClasses( clWater, 3, clPlayer, 6, clBaseResource, 4, + clPassage, 2, clCliffs, 1) ], scaleByMapSize(6, 25), 1000); log("Creating grass patches..."); createAreas( new ClumpPlacer(20, 0.3, 0.06, 0.5), [ new TerrainPainter(tLushGrass), paintClass(clForest) ], avoidClasses( clWater, 1, clPlayer, 6, clBaseResource, 3, clCliffs, 1), scaleByMapSize(10, 40)); log("Creating forests..."); createObjectGroupsDeprecated( new SimpleGroup( [ new SimpleObject(ePine, 3, 6, 1, 3), new SimpleObject(ePalmTall, 1, 3, 1, 3), new SimpleObject(eFanPalm, 0, 2, 0, 2), new SimpleObject(eApple, 0, 1, 1, 2) ], true, clForest), 0, [ stayClasses(clIsland, 3), avoidClasses( clWater, 1, clForest, 0, clPlayer, 6, clBaseResource, 4, + clPassage, 2, clCliffs, 2) ], scaleByMapSize(350, 2500), 100); Engine.SetProgress(75); log("Creating small decorative rocks..."); createObjectGroupsDeprecated( new SimpleGroup( [ new SimpleObject(aRock, 1, 3, 0, 1), new SimpleObject(aStandingStone, 0, 2, 0, 3) ], true), 0, avoidClasses( clWater, 0, clForest, 0, clPlayer, 6, clBaseResource, 4, clPassage, 2), scaleByMapSize(16, 262), 50); log("Creating large decorative rocks..."); var rocksGroup = new SimpleGroup( [ new SimpleObject(aLargeRock, 1, 2, 0, 1), new SimpleObject(aRock, 1, 3, 0, 2) ], true); createObjectGroupsDeprecated( rocksGroup, 0, avoidClasses( clWater, 0, clForest, 0, clPlayer, 6, clBaseResource, 4, clPassage, 2), scaleByMapSize(8, 131), 50); createObjectGroupsDeprecated( rocksGroup, 0, borderClasses(clWater, 5, 10), scaleByMapSize(100, 800), 500); log("Creating decorative plants..."); var plantGroups = [ new SimpleGroup( [ new SimpleObject(aPlantA, 3, 7, 0, 3), new SimpleObject(aPlantB, 3,6, 0, 3), new SimpleObject(aPlantC, 1,4, 0, 4) ], true), new SimpleGroup( [ new SimpleObject(aPlantB, 5, 20, 0, 5), new SimpleObject(aPlantC, 4,10, 0,4) ], true) ]; for (let group of plantGroups) createObjectGroupsDeprecated( group, 0, avoidClasses( clWater, 0, clBaseResource, 4, clShore, 3), scaleByMapSize(100, 600), 50); Engine.SetProgress(80); log("Creating animals..."); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(ePig, 2,4, 0,3)]), 0, avoidClasses( clWater, 3, clBaseResource, 4, clPlayer, 6), scaleByMapSize(20, 100), 50); log("Creating fish..."); createObjectGroupsDeprecated( new SimpleGroup([new SimpleObject(eFish, 1,2, 0,3)]), 0, [ stayClasses(clWater, 3), avoidClasses(clCreek, 3, clShore, 3) ], scaleByMapSize(50, 150), 100); Engine.SetProgress(95); setSkySet(pickRandom(["cumulus", "sunny"])); setSunColor(0.8, 0.66, 0.48); setSunElevation(0.828932); setSunRotation((swapAngle ? 0.288 : 0.788) * Math.PI); setTerrainAmbientColor(0.564706,0.543726,0.419608); setUnitsAmbientColor(0.53,0.55,0.45); setWaterColor(0.2,0.294,0.49); setWaterTint(0.208, 0.659, 0.925); setWaterMurkiness(0.72); setWaterWaviness(2.0); setWaterType("ocean"); -ExportMap(); -// this function will go from point [x1,z1] to point [x2,z2], while following a curve of width (starting-center-starting) -// it can smooth on the side depending on "smooth", which is the distance of the smooth. Tileclass and Terrain set a tileclass/terrain -// it effectively can create a smooth path from point [x1,z1] to point [x2,z2], ie Canyon, whatever. -// note: NOT efficient for large distances: I'm widely oversampling -function straightPassageMaker(x1, z1, x2, z2, startWidth, centerWidth, smooth, tileclass, terrain) -{ - var mapSize = g_Map.size; - var stepNB = sqrt((x2-x1)*(x2-x1) + (z2-z1)*(z2-z1)) + 2; - - var startHeight = getHeight(x1,z1); - var finishHeight = getHeight(x2,z2); - for (var step = 0; step <= stepNB; step+=0.5) - { - var ix = ((stepNB-step)*x1 + x2*step) / stepNB; - var iz = ((stepNB-step)*z1 + z2*step) / stepNB; - - // 5 at star/end, and 0 at the center - var width = (abs(step - stepNB/2.0) *startWidth + (stepNB/2 - abs(step - stepNB/2.0)) * centerWidth ) / (stepNB/2); - var oldDirection = [x2-x1, z2-z1]; - - // let's get the perpendicular direction - var direction = [ -oldDirection[1],oldDirection[0] ]; - - if (abs(direction[0]) > abs(direction[1])) - { - direction[1] = direction[1] / abs(direction[0]); - if (direction[0] > 0) - direction[0] = 1; - else - direction[0] = -1; - } - else - { - direction[0] = direction[0] / abs(direction[1]); - if (direction[1] > 0) - direction[1] = 1; - else - direction[1] = -1; - } - - for (var po = -Math.floor(width/2.0); po <= Math.floor(width/2.0); po+=0.5) - { - var rx = po*direction[0]; - var rz = po*direction[1]; - - var targetHeight = ((stepNB-step)*startHeight + finishHeight*step) / stepNB; - - if (round(ix + rx) < mapSize && round(iz + rz) < mapSize && round(ix + rx) >= 0 && round(iz + rz) >= 0) - { - // smoothing the sides - if ( abs(abs(po) - abs(Math.floor(width/2.0))) < smooth) - { - var localHeight = getHeight(round(ix + rx), round(iz + rz)); - var localPart = smooth - abs(abs(po) - abs(Math.floor(width/2.0))); - var targetHeight = (localHeight * localPart + targetHeight * (1/localPart) )/ (localPart + 1/localPart); - } - - g_Map.setHeight(round(ix + rx), round(iz + rz), targetHeight); - - if (tileclass !== null) - addToClass(round(ix + rx), round(iz + rz), tileclass); - - if (terrain !== null) - placeTerrain(round(ix + rx), round(iz + rz), terrain); - } - } - } -} +ExportMap(); // no need for preliminary rounding function getHeightDiff(x1, z1) { var height = getHeight(round(x1),round(z1)); var diff = 0; if (z1 + 1 < mapSize) diff += abs(getHeight(round(x1),round(z1+1)) - height); if (x1 + 1 < mapSize && z1 + 1 < mapSize) diff += abs(getHeight(round(x1+1),round(z1+1)) - height); if (x1 + 1 < mapSize) diff += abs(getHeight(round(x1+1),round(z1)) - height); if (x1 + 1 < mapSize && z1 - 1 >= 0) diff += abs(getHeight(round(x1+1),round(z1-1)) - height); if (z1 - 1 >= 0) diff += abs(getHeight(round(x1),round(z1-1)) - height); if (x1 - 1 >= 0 && z1 - 1 >= 0) diff += abs(getHeight(round(x1-1),round(z1-1)) - height); if (x1 - 1 >= 0) diff += abs(getHeight(round(x1-1),round(z1)) - height); if (x1 - 1 >= 0 && z1 + 1 < mapSize) diff += abs(getHeight(round(x1-1),round(z1+1)) - height); return diff; } Index: ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js (revision 20731) +++ ps/trunk/binaries/data/mods/public/maps/random/pyrenean_sierra.js (revision 20732) @@ -1,613 +1,564 @@ Engine.LoadLibrary("rmgen"); TILE_CENTERED_HEIGHT_MAP = true; const tGrassSpecific = ["new_alpine_grass_d","new_alpine_grass_d", "new_alpine_grass_e"]; const tGrass = ["new_alpine_grass_d", "new_alpine_grass_b", "new_alpine_grass_e"]; const tGrassMidRange = ["new_alpine_grass_b", "alpine_grass_a"]; const tGrassHighRange = ["new_alpine_grass_a", "alpine_grass_a", "alpine_grass_rocky"]; const tHighRocks = ["alpine_cliff_b", "alpine_cliff_c","alpine_cliff_c", "alpine_grass_rocky"]; const tSnowedRocks = ["alpine_cliff_b", "alpine_cliff_snow"]; const tTopSnow = ["alpine_snow_rocky","alpine_snow_a"]; const tTopSnowOnly = ["alpine_snow_a"]; const tDirtyGrass = ["new_alpine_grass_d","alpine_grass_d","alpine_grass_c", "alpine_grass_b"]; const tLushGrass = ["new_alpine_grass_a","new_alpine_grass_d"]; const tMidRangeCliffs = ["alpine_cliff_b","alpine_cliff_c"]; const tHighRangeCliffs = ["alpine_mountainside","alpine_cliff_snow" ]; const tPass = ["alpine_cliff_b", "alpine_cliff_c", "alpine_grass_rocky", "alpine_grass_rocky", "alpine_grass_rocky"]; const tSand = ["beach_c", "beach_d"]; const tSandTransition = ["beach_scrub_50_"]; const tWater = ["sand_wet_a","sand_wet_b","sand_wet_b","sand_wet_b"]; const tGrassLandForest = "alpine_forrestfloor"; const tGrassLandForest2 = "alpine_grass_d"; const tForestTransition = ["new_alpine_grass_d", "new_alpine_grass_b","alpine_grass_d"]; const tRoad = "new_alpine_citytile"; const tRoadWild = "new_alpine_citytile"; const oBeech = "gaia/flora_tree_euro_beech"; const oPine = "gaia/flora_tree_aleppo_pine"; const oBerryBush = "gaia/flora_bush_berry"; const oDeer = "gaia/fauna_deer"; const oFish = "gaia/fauna_fish"; const oRabbit = "gaia/fauna_rabbit"; const oStoneLarge = "gaia/geology_stonemine_alpine_quarry"; const oStoneSmall = "gaia/geology_stone_alpine_a"; const oMetalLarge = "gaia/geology_metal_alpine_slabs"; const aGrass = "actor|props/flora/grass_soft_small_tall.xml"; const aGrassShort = "actor|props/flora/grass_soft_large.xml"; const aRockLarge = "actor|geology/stone_granite_med.xml"; const aRockMedium = "actor|geology/stone_granite_med.xml"; const aBushMedium = "actor|props/flora/bush_medit_me.xml"; const aBushSmall = "actor|props/flora/bush_medit_sm.xml"; const pForestLand = [tGrassLandForest + TERRAIN_SEPARATOR + oPine,tGrassLandForest + TERRAIN_SEPARATOR + oBeech, tGrassLandForest2 + TERRAIN_SEPARATOR + oPine,tGrassLandForest2 + TERRAIN_SEPARATOR + oBeech, tGrassLandForest,tGrassLandForest2,tGrassLandForest2,tGrassLandForest2]; const pForestLandLight = [tGrassLandForest + TERRAIN_SEPARATOR + oPine,tGrassLandForest + TERRAIN_SEPARATOR + oBeech, tGrassLandForest2 + TERRAIN_SEPARATOR + oPine,tGrassLandForest2 + TERRAIN_SEPARATOR + oBeech, tGrassLandForest,tGrassLandForest2,tForestTransition,tGrassLandForest2, tGrassLandForest,tForestTransition,tGrassLandForest2,tForestTransition, tGrassLandForest2,tGrassLandForest2,tGrassLandForest2,tGrassLandForest2]; const pForestLandVeryLight = [ tGrassLandForest2 + TERRAIN_SEPARATOR + oPine,tGrassLandForest2 + TERRAIN_SEPARATOR + oBeech, tForestTransition,tGrassLandForest2,tForestTransition,tForestTransition,tForestTransition, tGrassLandForest,tForestTransition,tGrassLandForest2,tForestTransition, tGrassLandForest2,tGrassLandForest2,tGrassLandForest2,tGrassLandForest2]; InitMap(); const numPlayers = getNumPlayers(); const mapSize = getMapSize(); const mapCenter = getMapCenter(); var clDirt = createTileClass(); var clLush = createTileClass(); var clRock = createTileClass(); var clMetal = createTileClass(); var clFood = createTileClass(); var clBaseResource = createTileClass(); var clPass = createTileClass(); var clPyrenneans = createTileClass(); var clPass = createTileClass(); var clPlayer = createTileClass(); var clHill = createTileClass(); var clForest = createTileClass(); var clWater = createTileClass(); // Initial Terrain Creation // I'll use very basic noised sinusoidal functions to give the terrain a way aspect // It looks like we can't go higher than ≈ 75. Given this I'll lower the ground const baseHeight = -6; setWaterHeight(8); var MoutainAngle = randFloat(0,TWO_PI); var oceanAngle = MoutainAngle + randFloat(-1, 1) * Math.PI / 12; var baseHeights = []; for (var ix = 0; ix < mapSize; ix++) { baseHeights.push([]); for (var iz = 0; iz < mapSize; iz++) { if (g_Map.inMapBounds(ix,iz)) { placeTerrain(ix, iz, tGrass); setHeight(ix,iz,baseHeight +randFloat(-1,1) + scaleByMapSize(1,3)*(cos(ix/scaleByMapSize(5,30))+sin(iz/scaleByMapSize(5,30)))); baseHeights[ix].push( baseHeight +randFloat(-1,1) + scaleByMapSize(1,3)*(cos(ix/scaleByMapSize(5,30))+sin(iz/scaleByMapSize(5,30))) ); } else baseHeights[ix].push(-100); } } var playerIDs = primeSortAllPlayers(); var [playerX, playerZ] = playerPlacementCustomAngle( 0.35, tilesToFraction(mapCenter.x), tilesToFraction(mapCenter.y), i => oceanAngle + Math.PI * (i % 2 ? 1 : -1) * ((1/2 + 1/3 * (2/numPlayers * (i + 1 - i % 2) - 1)))); 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]); ix = round(fx); iz = round(fz); addCivicCenterAreaToClass(ix, iz, clPlayer); // 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); 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(oBerryBush, 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); var hillSize = PI * radius * radius; // create starting trees var num = floor(hillSize / 100); var tAngle = randFloat(-PI/3, 4*PI/3); var tDist = randFloat(11, 13); var tX = round(fx + tDist * cos(tAngle)); var tZ = round(fz + tDist * sin(tAngle)); group = new SimpleGroup( [new SimpleObject(oPine, num, num, 0,5)], false, clBaseResource, tX, tZ ); createObjectGroup(group, 0, avoidClasses(clBaseResource,2)); placeDefaultDecoratives(fx, fz, aGrassShort, clBaseResource, radius); } Engine.SetProgress(30); log ("Creating the pyreneans..."); -var MountainStartX = mapCenter.x + Math.cos(MoutainAngle) * fractionToTiles(0.34); -var MountainStartZ = mapCenter.y + Math.sin(MoutainAngle) * fractionToTiles(0.34); -var MountainEndX = mapCenter.x - Math.cos(MoutainAngle) * fractionToTiles(0.34); -var MountainEndZ = mapCenter.y - Math.sin(MoutainAngle) * fractionToTiles(0.34); +var mountainLength = fractionToTiles(0.68); -var MountainHeight = scaleByMapSize(50,65); +var mountainVec = new Vector2D(mountainLength, 0).rotate(-MoutainAngle); +var mountainVecHalf = Vector2D.mult(mountainVec, 1/2); + +var mountainStart = Vector2D.add(mapCenter, mountainVecHalf); +var mountainEnd = Vector2D.sub(mapCenter, mountainVecHalf); // Number of peaks +var MountainHeight = scaleByMapSize(50, 65); var NumOfIterations = scaleByMapSize(100,1000); var randomNess = randFloat(-scaleByMapSize(1,12),scaleByMapSize(1,12)); for (var i = 0; i < NumOfIterations; i++) { Engine.SetProgress(45 * i/NumOfIterations + 30 * (1-i/NumOfIterations)); var position = i/NumOfIterations; var width = scaleByMapSize(15,55); var randHeight2 = randFloat(0,10) + MountainHeight; for (var dist = 0; dist < width*3; dist++) { var okDist = dist/3; - var S1x = round((MountainStartX * (1-position) + MountainEndX*position) + randomNess*cos(position*3.14*4) + cos(MoutainAngle+PI/2)*okDist); - var S1z = round((MountainStartZ * (1-position) + MountainEndZ*position) + randomNess*sin(position*3.14*4) + sin(MoutainAngle+PI/2)*okDist); - var S2x = round((MountainStartX * (1-position) + MountainEndX*position) + randomNess*cos(position*3.14*4) + cos(MoutainAngle-PI/2)*okDist); - var S2z = round((MountainStartZ * (1-position) + MountainEndZ*position) + randomNess*sin(position*3.14*4) + sin(MoutainAngle-PI/2)*okDist); + var S1x = round((mountainStart.x * (1-position) + mountainEnd.x*position) + randomNess*cos(position*3.14*4) + cos(MoutainAngle+PI/2)*okDist); + var S1z = round((mountainStart.y * (1-position) + mountainEnd.y*position) + randomNess*sin(position*3.14*4) + sin(MoutainAngle+PI/2)*okDist); + var S2x = round((mountainStart.x * (1-position) + mountainEnd.x*position) + randomNess*cos(position*3.14*4) + cos(MoutainAngle-PI/2)*okDist); + var S2z = round((mountainStart.y * (1-position) + mountainEnd.y*position) + randomNess*sin(position*3.14*4) + sin(MoutainAngle-PI/2)*okDist); // complicated sigmoid // Ranges is 0-1, FormX is 0-1 too. var FormX = (-2*(1-okDist/width)+1.9) - 4*(2*(1-okDist/width)-randFloat(0.9,1.1))*(2*(1-okDist/width)-randFloat(0.9,1.1))*(2*(1-okDist/width)-randFloat(0.9,1.1)); var Formula = (1/(1 + Math.exp(FormX))); // If we're too far from the border, we flatten Formula *= (0.2 - Math.max(0,abs(0.5 - position) - 0.3)) * 5; var randHeight = randFloat(-9,9) * Formula; var height = baseHeights[S1x][S1z]; setHeight(S1x,S1z, height + randHeight2 * Formula + randHeight ); var height = baseHeights[S2x][S2z]; setHeight(S2x,S2z, height + randHeight2 * Formula + randHeight ); if (getHeight(S1x,S1z) > 15) addToClass(S1x,S1z, clPyrenneans); if (getHeight(S2x,S2z) > 15) addToClass(S2x,S2z, clPyrenneans); } } // Allright now slight smoothing (decreasing with height) for (var ix = 1; ix < mapSize-1; ix++) { for (var iz = 1; iz < mapSize-1; iz++) { if (g_Map.inMapBounds(ix,iz) && checkIfInClass(ix,iz,clPyrenneans) ) { var NB = getNeighborsHeight(ix,iz); var index = 9/(1 + Math.max(0,getHeight(ix,iz)/7)); setHeight(ix,iz, (getHeight(ix,iz)*(9-index) + NB*index)/9 ); } } } Engine.SetProgress(48); -// Okay so the mountains are pretty much here. -// Making the passes +log("Creating passages..."); +var passageLocation = 0.35; +var passageHeight = MountainHeight - 25; +var passageLength = scaleByMapSize(8, 50); +var passageVec = mountainVec.perpendicular().normalize().mult(passageLength); -var passWidth = scaleByMapSize(15,100) /1.8; -var S1x = round((MountainStartX * (0.35) + MountainEndX*0.65) + cos(MoutainAngle+PI/2)*passWidth); -var S1z = round((MountainStartZ * (0.35) + MountainEndZ*0.65) + sin(MoutainAngle+PI/2)*passWidth); -var S2x = round((MountainStartX * (0.35) + MountainEndX*0.65) + cos(MoutainAngle-PI/2)*passWidth); -var S2z = round((MountainStartZ * (0.35) + MountainEndZ*0.65) + sin(MoutainAngle-PI/2)*passWidth); -PassMaker(S1x, S1z, S2x, S2z, 4, 7, (getHeight(S1x,S1z) + getHeight(S2x,S2z))/2.0, MountainHeight-25, 2, clPass); - -S1x = round((MountainStartX * (0.65) + MountainEndX*0.35) + cos(MoutainAngle+PI/2)*passWidth); -S1z = round((MountainStartZ * (0.65) + MountainEndZ*0.35) + sin(MoutainAngle+PI/2)*passWidth); -S2x = round((MountainStartX * (0.65) + MountainEndX*0.35) + cos(MoutainAngle-PI/2)*passWidth); -S2z = round((MountainStartZ * (0.65) + MountainEndZ*0.35) + sin(MoutainAngle-PI/2)*passWidth); -PassMaker(S1x, S1z, S2x, S2z, 4, 7, (getHeight(S1x,S1z) + getHeight(S2x,S2z))/2.0, MountainHeight-25, 2, clPass); +for (let passLoc of [passageLocation, 1 - passageLocation]) + for (let direction of [1, -1]) + { + let passageStart = Vector2D.add(mountainEnd, Vector2D.mult(mountainVec, passLoc)); + let passageEnd = Vector2D.add(passageStart, Vector2D.mult(passageVec, direction)); + + createPassage({ + "start": passageStart, + "end": passageEnd, + "startHeight": passageHeight, + "startWidth": 7, + "endWidth": 7, + "smoothWidth": 2, + "tileClass": clPass + }); + } Engine.SetProgress(50); // Smoothing the mountains for (var ix = 1; ix < mapSize-1; ix++) for (var iz = 1; iz < mapSize-1; iz++) { if ( g_Map.inMapBounds(ix,iz) && checkIfInClass(ix,iz,clPyrenneans) ) { var NB = getNeighborsHeight(ix,iz); var index = 9/(1 + Math.max(0,(getHeight(ix,iz)-10)/7)); setHeight(ix,iz, (getHeight(ix,iz)*(9-index) + NB*index)/9 ); baseHeights[ix][iz] = (getHeight(ix,iz)*(9-index) + NB*index)/9; } } log("Creating oceans..."); let [oceanX, oceanZ] = distributePointsOnCircle(2, oceanAngle, fractionToTiles(0.48), mapCenter.x, mapCenter.y); for (let i in oceanX) createArea( new ClumpPlacer(diskArea(fractionToTiles(0.18)), 0.9, 0.05, 10, oceanX[i], oceanZ[i]), [ new ElevationPainter(-22), paintClass(clWater) ]); // Smoothing around the water, then going a bit random for (var ix = 1; ix < mapSize-1; ix++) { for (var iz = 1; iz < mapSize-1; iz++) { if ( g_Map.inMapBounds(ix,iz) && getTileClass(clWater).countInRadius(ix,iz,5,true) > 0 ) { var averageHeight = 0; var size = 5; if (getTileClass(clPyrenneans).countInRadius(ix,iz,1,true) > 0) size = 1; else if (getTileClass(clPyrenneans).countInRadius(ix,iz,2,true) > 0) size = 2; else if (getTileClass(clPyrenneans).countInRadius(ix,iz,3,true) > 0) size = 3; else if (getTileClass(clPyrenneans).countInRadius(ix,iz,4,true) > 0) size = 4; var todivide = 0; for (var xx = -size; xx <= size;xx++) for (var yy = -size; yy <= size;yy++) { if (g_Map.inMapBounds(ix + xx,iz + yy) && (xx != 0 || yy != 0)){ averageHeight += getHeight(ix + xx,iz + yy) / (abs(xx)+abs(yy)); todivide += 1/(abs(xx)+abs(yy)); } } averageHeight += getHeight(ix,iz)*2; averageHeight /= (todivide+2); setHeight(ix,iz, averageHeight ); //baseHeights[ix][iz] = averageHeight; } if ( g_Map.inMapBounds(ix,iz) && getTileClass(clWater).countInRadius(ix,iz,4,true) > 0 && getTileClass(clWater).countInRadius(ix,iz,4) > 0 ) setHeight(ix,iz, getHeight(ix,iz) + randFloat(-1,1)); } } Engine.SetProgress(55); log ("Creating hills..."); createAreas( new ClumpPlacer(scaleByMapSize(60, 120), 0.3, 0.06, 5), [ new SmoothElevationPainter(ELEVATION_MODIFY, 7, 4, 2), new TerrainPainter(tGrassSpecific), paintClass(clHill) ], avoidClasses(clWater, 5, clPlayer, 20, clBaseResource, 6, clPyrenneans, 2), scaleByMapSize(5, 35)); log("Creating forests..."); var types = [[tForestTransition, pForestLandVeryLight, pForestLandLight, pForestLand]]; var size = scaleByMapSize(40,115)*PI; var num = floor(scaleByMapSize(8,40) / types.length); for (let type of types) createAreas( new ClumpPlacer(size, 0.2, 0.1, 1), [ new LayeredPainter(type, [scaleByMapSize(1, 2), scaleByMapSize(3, 6), scaleByMapSize(3, 6)]), paintClass(clForest) ], avoidClasses(clPlayer, 20, clPyrenneans,0, clForest, 7, clWater, 2), num); Engine.SetProgress(60); log("Creating lone trees..."); var num = scaleByMapSize(80,400); var group = new SimpleGroup([new SimpleObject(oPine, 1,2, 1,3),new SimpleObject(oBeech, 1,2, 1,3)], true, clForest); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 1, clPlayer, 8,clPyrenneans, 1), num, 20 ); log("Painting the map..."); for (let x = 0; x < mapSize; ++x) for (let z = 0; z < mapSize; ++z) { let height = getHeight(x, z); let heightDiff = getHeightDifference(x, z); if (getTileClass(clPyrenneans).countInRadius(x, z, 2, true)) { createTerrain(getPyreneansTerrain(height, heightDiff)).place(x, z); if (height >= 30 && heightDiff < 5 && getTileClass(clPass).countInRadius(x, z, 2, true)) createTerrain(tPass).place(x,z); } let terrainShore = getShoreTerrain(height, heightDiff, x, z); if (terrainShore) createTerrain(terrainShore).place(x, z); } function getPyreneansTerrain(height, heightDiff) { if (height < 6) return heightDiff < 5 ? tGrass : tMidRangeCliffs; if (height < 18) return heightDiff < 8 ? tGrassMidRange : tMidRangeCliffs; if (height < 30) return heightDiff < 8 ? tGrassHighRange : tMidRangeCliffs; if (height < MountainHeight - 20) return heightDiff < 8 ? tHighRocks : tHighRangeCliffs; if (height < MountainHeight - 10) return heightDiff < 7 ? tSnowedRocks : tHighRangeCliffs; return heightDiff < 6 ? tTopSnowOnly : tTopSnow; } function getShoreTerrain(height, heightDiff, x, z) { if (height <= -14) return tWater; if (height <= -2 && getTileClass(clWater).countInRadius(x, z, 2, true)) return heightDiff < 2.5 ? tSand : tMidRangeCliffs; if (height <= 0 && getTileClass(clWater).countInRadius(x, z, 3, true)) return heightDiff < 2.5 ? tSandTransition : tMidRangeCliffs; return undefined; } log("Creating dirt patches..."); for (let size of [scaleByMapSize(3, 20), scaleByMapSize(5, 40), scaleByMapSize(8, 60)]) createAreas( new ClumpPlacer(size, 0.3, 0.06, 0.5), [ new TerrainPainter(tDirtyGrass), paintClass(clDirt) ], avoidClasses(clWater, 3, clForest, 0, clPyrenneans,5, clHill, 0, clDirt, 5, clPlayer, 6), 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(tLushGrass), paintClass(clLush) ], avoidClasses(clWater, 3, clForest, 0, clPyrenneans,5, clHill, 0, clDirt, 5, clPlayer, 6), scaleByMapSize(15, 45)); Engine.SetProgress(70); // making more in dirt areas so as to appear different log("Creating small grass tufts..."); var group = new SimpleGroup( [new SimpleObject(aGrassShort, 1,2, 0,1, -PI/8,PI/8)] ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 2, clHill, 2, clPlayer, 5, clDirt, 0, clPyrenneans,2), scaleByMapSize(13, 200) ); createObjectGroupsDeprecated(group, 0, stayClasses(clDirt,1), scaleByMapSize(13, 200),10); 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(clWater, 3, clHill, 2, clPlayer, 5, clDirt, 1, clForest, 0, clPyrenneans,2), scaleByMapSize(13, 200) ); createObjectGroupsDeprecated(group, 0, stayClasses(clDirt,1), scaleByMapSize(13, 200),10); Engine.SetProgress(75); log("Creating bushes..."); group = new SimpleGroup( [new SimpleObject(aBushMedium, 1,2, 0,2), new SimpleObject(aBushSmall, 2,4, 0,2)] ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 2, clPlayer, 1, clPyrenneans, 1), scaleByMapSize(13, 200), 50 ); Engine.SetProgress(80); 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(clWater, 3, clForest, 1, clPlayer, 20, clRock, 8, clPyrenneans, 1), 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(clWater, 3, clForest, 1, clPlayer, 20, clRock, 8, clPyrenneans, 1), scaleByMapSize(4,16), 100 ); log("Creating metal mines..."); group = new SimpleGroup([new SimpleObject(oMetalLarge, 1,1, 0,4)], true, clMetal); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 1, clPlayer, 20, clMetal, 8, clRock, 5, clPyrenneans, 1), scaleByMapSize(4,16), 100 ); Engine.SetProgress(85); log("Creating small decorative rocks..."); group = new SimpleGroup( [new SimpleObject(aRockMedium, 1,3, 0,1)], true ); createObjectGroupsDeprecated( group, 0, avoidClasses(clWater, 0, clForest, 0, clPlayer, 0), 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(clWater, 0, clForest, 0, clPlayer, 0), scaleByMapSize(8, 131), 50 ); Engine.SetProgress(90); log("Creating deer..."); group = new SimpleGroup( [new SimpleObject(oDeer, 5,7, 0,4)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clPyrenneans, 1, clFood, 15), 3 * numPlayers, 50 ); log("Creating rabbit..."); group = new SimpleGroup( [new SimpleObject(oRabbit, 2,3, 0,2)], true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clPyrenneans, 1, clFood,15), 3 * numPlayers, 50 ); log("Creating berry bush..."); group = new SimpleGroup( [new SimpleObject(oBerryBush, 5,7, 0,4)],true, clFood ); createObjectGroupsDeprecated(group, 0, avoidClasses(clWater, 3, clForest, 0, clPlayer, 20, clPyrenneans, 1, clFood, 10), 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(clFood, 15), stayClasses(clWater, 6)], 20 * numPlayers, 60 ); setSunElevation(randFloat(PI/5, PI / 3)); setSunRotation(randFloat(0, TWO_PI)); setSkySet("cumulus"); setSunColor(0.73,0.73,0.65); setTerrainAmbientColor(0.45,0.45,0.50); setUnitsAmbientColor(0.4,0.4,0.4); setWaterColor(0.263, 0.353, 0.616); setWaterTint(0.104, 0.172, 0.563); setWaterWaviness(5.0); setWaterType("ocean"); setWaterMurkiness(0.83); ExportMap(); function getNeighborsHeight(x1, z1) { var toCheck = [ [-1,-1], [-1,0], [-1,1], [0,1], [1,1], [1,0], [1,-1], [0,-1] ]; var height = 0; for (var i in toCheck) { var xx = x1 + toCheck[i][0]; var zz = z1 + toCheck[i][1]; height += getHeight(round(xx),round(zz)); } height /= 8; return height; } -// Taken from Corsica vs Sardinia with tweaks -function PassMaker(x1, z1, x2, z2, startWidth, centerWidth, startElevation, centerElevation, smooth, tileclass, terrain) -{ - var mapSize = g_Map.size; - var stepNB = sqrt((x2-x1)*(x2-x1) + (z2-z1)*(z2-z1)) + 2; - var startHeight = startElevation; - var finishHeight = centerElevation; - - for (var step = 0; step <= stepNB; step+=0.5) - { - var ix = ((stepNB-step)*x1 + x2*step) / stepNB; - var iz = ((stepNB-step)*z1 + z2*step) / stepNB; - - var width = (abs(step - stepNB/2.0) *startWidth + (stepNB/2 - abs(step - stepNB/2.0)) * centerWidth ) / (stepNB/2); - - var oldDirection = [x2-x1, z2-z1]; - // let's get the perpendicular direction - var direction = [ -oldDirection[1],oldDirection[0] ]; - if (abs(direction[0]) > abs(direction[1])) - { - direction[1] = direction[1] / abs(direction[0]); - if (direction[0] > 0) - direction[0] = 1; - else - direction[0] = -1; - } else { - direction[0] = direction[0] / abs(direction[1]); - if (direction[1] > 0) - direction[1] = 1; - else - direction[1] = -1; - } - for (var po = -Math.floor(width/2.0); po <= Math.floor(width/2.0); po+=0.5) - { - var rx = po*direction[0]; - var rz = po*direction[1]; - - var targetHeight = (abs(step - stepNB/2.0) *startHeight + (stepNB/2 - abs(step - stepNB/2.0)) * finishHeight ) / (stepNB/2); - if (round(ix + rx) < mapSize && round(iz + rz) < mapSize && round(ix + rx) >= 0 && round(iz + rz) >= 0) - { - // smoothing the sides - if ( abs(abs(po) - abs(Math.floor(width/2.0))) < smooth) - { - var localHeight = getHeight(round(ix + rx), round(iz + rz)); - var localPart = smooth - abs(abs(po) - abs(Math.floor(width/2.0))); - var targetHeight = (localHeight * localPart + targetHeight * (1/localPart) )/ (localPart + 1/localPart); - } - - g_Map.setHeight(round(ix + rx), round(iz + rz), targetHeight); - if (tileclass != null) - addToClass(round(ix + rx), round(iz + rz), tileclass); - if (terrain != null) - placeTerrain(round(ix + rx), round(iz + rz), terrain); - } - } - } -} // no need for preliminary rounding function getHeightDifference(x1, z1) { x1 = round(x1); z1 = round(z1); var height = getHeight(x1,z1); if (!g_Map.inMapBounds(x1,z1)) return 0; // I wanna store the height difference with any neighbor var toCheck = [ [-1,-1], [-1,0], [-1,1], [0,1], [1,1], [1,0], [1,-1], [0,-1] ]; var diff = 0; var todiv = 0; for (var i in toCheck) { var xx = round(x1 + toCheck[i][0]); var zz = round(z1 + toCheck[i][1]); if (g_Map.inMapBounds(xx,zz)) { diff += abs(getHeight(xx,zz) - height); todiv++; } } if (todiv > 0) diff /= todiv; return diff; } - Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/gaia_terrain.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/gaia_terrain.js (revision 20731) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/gaia_terrain.js (revision 20732) @@ -1,699 +1,755 @@ /** * @file These functions are often used to create a landscape, for instance shaping mountains, hills, rivers or grass and dirt patches. */ /** * Bumps add slight, diverse elevation differences to otherwise completely level terrain. */ function createBumps(constraint, count, minSize, maxSize, spread, failFraction = 0, elevation = 2) { log("Creating bumps..."); createAreas( new ChainPlacer( minSize || 1, maxSize || Math.floor(scaleByMapSize(4, 6)), spread || Math.floor(scaleByMapSize(2, 5)), failFraction), new SmoothElevationPainter(ELEVATION_MODIFY, elevation, 2), constraint, count || scaleByMapSize(100, 200)); } /** * Hills are elevated, planar, impassable terrain areas. */ function createHills(terrainset, constraint, tileClass, count, minSize, maxSize, spread, failFraction = 0.5, elevation = 18, elevationSmoothing = 2) { log("Creating hills..."); createAreas( new ChainPlacer( minSize || 1, maxSize || Math.floor(scaleByMapSize(4, 6)), spread || Math.floor(scaleByMapSize(16, 40)), failFraction), [ new LayeredPainter(terrainset, [1, elevationSmoothing]), new SmoothElevationPainter(ELEVATION_SET, elevation, elevationSmoothing), paintClass(tileClass) ], constraint, count || scaleByMapSize(1, 4) * getNumPlayers()); } /** * Mountains are impassable smoothened cones. */ function createMountains(terrain, constraint, tileClass, count, maxHeight, minRadius, maxRadius, numCircles) { log("Creating mountains..."); let mapSize = getMapSize(); for (let i = 0; i < (count || scaleByMapSize(1, 4) * getNumPlayers()); ++i) createMountain( maxHeight !== undefined ? maxHeight : Math.floor(scaleByMapSize(30, 50)), minRadius || Math.floor(scaleByMapSize(3, 4)), maxRadius || Math.floor(scaleByMapSize(6, 12)), numCircles || Math.floor(scaleByMapSize(4, 10)), constraint, randIntExclusive(0, mapSize), randIntExclusive(0, mapSize), terrain, tileClass, 14); } /** * Create a mountain using a technique very similar to ChainPlacer. */ function createMountain(maxHeight, minRadius, maxRadius, numCircles, constraint, x, z, terrain, tileClass, fcc = 0, q = []) { if (constraint instanceof Array) constraint = new AndConstraint(constraint); if (!g_Map.inMapBounds(x, z) || !constraint.allows(x, z)) return; let mapSize = getMapSize(); let queueEmpty = !q.length; let gotRet = []; for (let i = 0; i < mapSize; ++i) { gotRet[i] = []; for (let j = 0; j < mapSize; ++j) gotRet[i][j] = -1; } --mapSize; minRadius = Math.max(1, Math.min(minRadius, maxRadius)); let edges = [[x, z]]; let circles = []; for (let i = 0; i < numCircles; ++i) { let badPoint = false; let [cx, cz] = pickRandom(edges); let radius; if (queueEmpty) radius = randIntInclusive(minRadius, maxRadius); else { radius = q.pop(); queueEmpty = !q.length; } let sx = Math.max(0, cx - radius); let sz = Math.max(0, cz - radius); let lx = Math.min(cx + radius, mapSize); let lz = Math.min(cz + radius, mapSize); let radius2 = Math.square(radius); for (let ix = sx; ix <= lx; ++ix) { for (let iz = sz; iz <= lz; ++iz) { if (Math.euclidDistance2D(ix, iz, cx, cz) > radius2 || !g_Map.inMapBounds(ix, iz)) continue; if (!constraint.allows(ix, iz)) { badPoint = true; break; } let state = gotRet[ix][iz]; if (state == -1) { gotRet[ix][iz] = -2; } else if (state >= 0) { edges.splice(state, 1); gotRet[ix][iz] = -2; for (let k = state; k < edges.length; ++k) --gotRet[edges[k][0]][edges[k][1]]; } } if (badPoint) break; } if (badPoint) continue; circles.push([cx, cz, radius]); for (let ix = sx; ix <= lx; ++ix) for (let iz = sz; iz <= lz; ++iz) { if (gotRet[ix][iz] != -2 || fcc && (x - ix > fcc || ix - x > fcc || z - iz > fcc || iz - z > fcc) || ix > 0 && gotRet[ix-1][iz] == -1 || iz > 0 && gotRet[ix][iz-1] == -1 || ix < mapSize && gotRet[ix+1][iz] == -1 || iz < mapSize && gotRet[ix][iz+1] == -1) continue; edges.push([ix, iz]); gotRet[ix][iz] = edges.length - 1; } } for (let [cx, cz, radius] of circles) { let sx = Math.max(0, cx - radius); let sz = Math.max(0, cz - radius); let lx = Math.min(cx + radius, mapSize); let lz = Math.min(cz + radius, mapSize); let clumpHeight = radius / maxRadius * maxHeight * randFloat(0.8, 1.2); for (let ix = sx; ix <= lx; ++ix) for (let iz = sz; iz <= lz; ++iz) { let distance = Math.euclidDistance2D(ix, iz, cx, cz); let newHeight = randIntInclusive(0, 2) + Math.round(2/3 * clumpHeight * (Math.sin(Math.PI * 2/3 * (3/4 - distance / radius)) + 0.5)); if (distance > radius) continue; if (getHeight(ix, iz) < newHeight) setHeight(ix, iz, newHeight); else if (getHeight(ix, iz) >= newHeight && getHeight(ix, iz) < newHeight + 4) setHeight(ix, iz, newHeight + 4); if (terrain !== undefined) placeTerrain(ix, iz, terrain); if (tileClass !== undefined) addToClass(ix, iz, tileClass); } } } /** * Generates a volcano mountain. Smoke and lava are optional. * * @param {number} fx - Horizontal coordinate of the center. * @param {number} fz - Horizontal coordinate of the center. * @param {number} tileClass - Painted onto every tile that is occupied by the volcano. * @param {string} terrainTexture - The texture painted onto the volcano hill. * @param {array} lavaTextures - Three different textures for the interior, from the outside to the inside. * @param {boolean} smoke - Whether to place smoke particles. * @param {number} elevationType - Elevation painter type, ELEVATION_SET = absolute or ELEVATION_MODIFY = relative. */ function createVolcano(fx, fz, tileClass, terrainTexture, lavaTextures, smoke, elevationType) { log("Creating volcano"); let ix = Math.round(fractionToTiles(fx)); let iz = Math.round(fractionToTiles(fz)); let baseSize = getMapArea() / scaleByMapSize(1, 8); let coherence = 0.7; let smoothness = 0.05; let failFraction = 100; let steepness = 3; let clLava = createTileClass(); let layers = [ { "clumps": 0.067, "elevation": 15, "tileClass": tileClass }, { "clumps": 0.05, "elevation": 25, "tileClass": createTileClass() }, { "clumps": 0.02, "elevation": 45, "tileClass": createTileClass() }, { "clumps": 0.011, "elevation": 62, "tileClass": createTileClass() }, { "clumps": 0.003, "elevation": 42, "tileClass": clLava, "painter": lavaTextures && new LayeredPainter([terrainTexture, ...lavaTextures], [1, 1, 1]), "steepness": 1 } ]; for (let i = 0; i < layers.length; ++i) createArea( new ClumpPlacer(baseSize * layers[i].clumps, coherence, smoothness, failFraction, ix, iz), [ layers[i].painter || new LayeredPainter([terrainTexture, terrainTexture], [3]), new SmoothElevationPainter(elevationType, layers[i].elevation, layers[i].steepness || steepness), paintClass(layers[i].tileClass) ], i == 0 ? null : stayClasses(layers[i - 1].tileClass, 1)); if (smoke) { let num = Math.floor(baseSize * 0.002); createObjectGroup( new SimpleGroup( [new SimpleObject("actor|particle/smoke.xml", num, num, 0, 7)], false, clLava, ix, iz), 0, stayClasses(tileClass, 1)); } } /** * Paint the given terrain texture in the given sizes at random places of the map to diversify monotone land texturing. */ function createPatches(sizes, terrain, constraint, count, tileClass, failFraction = 0.5) { for (let size of sizes) createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), size, failFraction), [ new TerrainPainter(terrain), paintClass(tileClass) ], constraint, count); } /** * Same as createPatches, but each patch consists of a set of textures drawn depending to the distance of the patch border. */ function createLayeredPatches(sizes, terrains, terrainWidths, constraint, count, tileClass, failFraction = 0.5) { for (let size of sizes) createAreas( new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), size, failFraction), [ new LayeredPainter(terrains, terrainWidths), paintClass(tileClass) ], constraint, count); } /** * Creates a meandering river at the given location and width. * Optionally calls a function on the affected tiles. * Horizontal locations and widths (including fadeDist, meandering) are fractions of the mapsize. * * @property horizontal - Whether the river is horizontal or vertical * @property parallel - Whether the shorelines should be parallel or meander separately. * @property position - Location of the river. * @property width - Size between the two shorelines. * @property fadeDist - Size of the shoreline. * @property deviation - Fuzz effect on the shoreline if greater than 0. * @property waterHeight - Ground height of the riverbed. * @proeprty landHeight - Ground height of the end of the shoreline. * @property meanderShort - Strength of frequent meanders. * @property meanderLong - Strength of less frequent meanders. * @property [constraint] - If given, ignores any tiles that don't satisfy the given Constraint. * @property [waterFunc] - Optional function called on tiles within the river. * Provides location on the tilegrid, new elevation and * the location on the axis parallel to the river as a fraction of the river length. * @property [landFunc] - Optional function called on land tiles, providing ix, iz, shoreDist1, shoreDist2. * @property [minHeight] - If given, only changes the elevation below this height while still calling the given functions. */ function paintRiver(args) { log("Creating the river"); // Model the river meandering as the sum of two sine curves. let meanderShort = fractionToTiles(args.meanderShort / scaleByMapSize(35, 160)); let meanderLong = fractionToTiles(args.meanderLong / scaleByMapSize(35, 100)); // Unless the river is parallel, each riverside will receive an own random seed and starting angle. let seed1 = randFloat(2, 3); let seed2 = randFloat(2, 3); let startingAngle1 = randFloat(0, 1); let startingAngle2 = randFloat(0, 1); // Computes the deflection of the river at a given point. let riverCurve = (riverFraction, startAngle, seed) => meanderShort * rndRiver(startAngle + fractionToTiles(riverFraction) / 128, seed) + meanderLong * rndRiver(startAngle + fractionToTiles(riverFraction) / 256, seed); // Describe river width and length of the shoreline. let halfWidth = fractionToTiles(args.width / 2); let fadeDist = fractionToTiles(args.fadeDist); // Describe river location in vectors. let mapSize = getMapSize(); let vecStart = new Vector2D(args.startX, args.startZ).mult(mapSize); let vecEnd = new Vector2D(args.endX, args.endZ).mult(mapSize); let riverLength = vecStart.distanceTo(vecEnd); let unitVecRiver = Vector2D.sub(vecStart, vecEnd).normalize(); // Describe river boundaries. let riverMinX = Math.min(vecStart.x, vecEnd.x); let riverMinZ = Math.min(vecStart.y, vecEnd.y); let riverMaxX = Math.max(vecStart.x, vecEnd.x); let riverMaxZ = Math.max(vecStart.y, vecEnd.y); for (let ix = 0; ix < mapSize; ++ix) for (let iz = 0; iz < mapSize; ++iz) { if (args.constraint && !args.constraint.allows(ix, iz)) continue; let vecPoint = new Vector2D(ix, iz); // Compute the shortest distance to the river. let distanceToRiver = unitVecRiver.cross(Vector2D.sub(vecPoint, vecEnd)); // Closest point on the river (i.e the foot of the perpendicular). let river = Vector2D.sub(vecPoint, unitVecRiver.perpendicular().mult(distanceToRiver)); // Only process points that actually are perpendicular with the river. if (river.x < riverMinX || river.x > riverMaxX || river.y < riverMinZ || river.y > riverMaxZ) continue; // Coordinate between 0 and 1 on the axis parallel to the river. let riverFraction = river.distanceTo(vecStart) / riverLength; // Amplitude of the river at this location. let riverCurve1 = riverCurve(riverFraction, startingAngle1, seed1); let riverCurve2 = args.parallel ? riverCurve1 : riverCurve(riverFraction, startingAngle2, seed2); // Add noise. let deviation = fractionToTiles(args.deviation) * randFloat(-1, 1); // Compute the distance to the shoreline. let sign = Math.sign(distanceToRiver || 1); let shoreDist1 = sign * riverCurve1 + Math.abs(distanceToRiver) - deviation - halfWidth; let shoreDist2 = sign * riverCurve2 + Math.abs(distanceToRiver) - deviation + halfWidth; // Create the elevation for the water and the slopy shoreline and call the user functions. if (shoreDist1 < 0 && shoreDist2 > 0) { let height = args.waterHeight; if (shoreDist1 > -fadeDist) height += (args.landHeight - args.waterHeight) * (1 + shoreDist1 / fadeDist); else if (shoreDist2 < fadeDist) height += (args.landHeight - args.waterHeight) * (1 - shoreDist2 / fadeDist); if (args.minHeight === undefined || height < args.minHeight) setHeight(ix, iz, height); if (args.waterFunc) args.waterFunc(ix, iz, height, riverFraction); } else if (args.landFunc) args.landFunc(ix, iz, shoreDist1, shoreDist2); } } /** * Helper function to create a meandering river. * It works the same as sin or cos function with the difference that it's period is 1 instead of 2 pi. */ function rndRiver(f, seed) { let rndRw = seed; for (let i = 0; i <= f; ++i) rndRw = 10 * (rndRw % 1); let rndRr = f % 1; let retVal = (Math.floor(f) % 2 ? -1 : 1) * rndRr * (rndRr - 1); let rndRe = Math.floor(rndRw) % 5; if (rndRe == 0) retVal *= 2.3 * (rndRr - 0.5) * (rndRr - 0.5); else if (rndRe == 1) retVal *= 2.6 * (rndRr - 0.3) * (rndRr - 0.7); else if (rndRe == 2) retVal *= 22 * (rndRr - 0.2) * (rndRr - 0.3) * (rndRr - 0.3) * (rndRr - 0.8); else if (rndRe == 3) retVal *= 180 * (rndRr - 0.2) * (rndRr - 0.2) * (rndRr - 0.4) * (rndRr - 0.6) * (rndRr - 0.6) * (rndRr - 0.8); else if (rndRe == 4) retVal *= 2.6 * (rndRr - 0.5) * (rndRr - 0.7); return retVal; } /** * Add small rivers with shallows starting at a central river ending at the map border, if the given Constraint is met. */ function createTributaryRivers(horizontal, riverCount, riverWidth, waterHeight, heightRange, maxAngle, tributaryRiverTileClass, shallowTileClass, constraint) { log("Creating tributary rivers..."); let waviness = 0.4; let smoothness = scaleByMapSize(3, 12); let offset = 0.1; let tapering = 0.05; let riverConstraint = avoidClasses(tributaryRiverTileClass, 3); if (shallowTileClass) riverConstraint = new AndConstraint([riverConstraint, avoidClasses(shallowTileClass, 2)]); for (let i = 0; i < riverCount; ++i) { // Determining tributary start point let location = randFloat(tapering, 1 - tapering); let sign = randBool() ? 1 : -1; let angle = sign * randFloat(maxAngle, 2 * Math.PI - maxAngle); let distance = sign * tapering; let searchStart = [fractionToTiles(location), fractionToTiles(0.5 + distance)]; let searchEnd = [fractionToTiles(location), fractionToTiles(0.5 - distance)]; if (!horizontal) { searchStart.reverse(); searchEnd.reverse(); } let start = getTIPIADBON(searchStart, searchEnd, heightRange, 0.5, 4); if (!start) continue; let endX = fractionToTiles(0.5 + 0.5 * Math.cos(angle)); let endZ = fractionToTiles(0.5 + 0.5 * Math.sin(angle)); // Create river if (!createArea( new PathPlacer( Math.floor(start[0]), Math.floor(start[1]), Math.floor(endX), Math.floor(endZ), riverWidth, waviness, smoothness, offset, tapering), [ new SmoothElevationPainter(ELEVATION_SET, waterHeight, 4), paintClass(tributaryRiverTileClass) ], new AndConstraint([constraint, riverConstraint]))) continue; // Create small puddles at the map border to ensure players being separated createArea( new ClumpPlacer(Math.floor(diskArea(riverWidth / 2)), 0.95, 0.6, 10, endX, endZ), new SmoothElevationPainter(ELEVATION_SET, waterHeight, 3), constraint); } // Create shallows if (shallowTileClass) for (let z of [0.25, 0.75]) { let m1 = [Math.round(fractionToTiles(0.2)), Math.round(fractionToTiles(z))]; let m2 = [Math.round(fractionToTiles(0.8)), Math.round(fractionToTiles(z))]; if (!horizontal) { m1.reverse(); m2.reverse(); } createShallowsPassage(...m1, ...m2, scaleByMapSize(4, 8), -2, -2, 2, shallowTileClass, undefined, waterHeight); } } /** * Create shallow water between (x1, z1) and (x2, z2) of tiles below maxHeight. */ function createShallowsPassage(x1, z1, x2, z2, width, maxHeight, shallowHeight, smooth, tileClass, terrain, riverHeight) { let a = z1 - z2; let b = x2 - x1; let distance = Math.euclidDistance2D(x1, z1, x2, z2); let mapSize = getMapSize(); for (let ix = 0; ix < mapSize; ++ix) for (let iz = 0; iz < mapSize; ++iz) { let c = a * (ix - x1) + b * (iz - z1); let my = iz - b * c / Math.square(distance); let inline = 0; let dis; if (b == 0) { dis = Math.abs(ix - x1); if (iz >= Math.min(z1, z2) && iz <= Math.max(z1, z2)) inline = 1; } else if (my >= Math.min(z1, z2) && my <= Math.max(z1, z2)) { dis = Math.abs(c) / distance; inline = 1; } if (dis > width || !inline || getHeight(ix, iz) > maxHeight) continue; if (dis > width - smooth) setHeight(ix, iz, ((width - dis) * shallowHeight + riverHeight * (smooth - width + dis)) / smooth); else if (dis <= width - smooth) setHeight(ix, iz, shallowHeight); if (tileClass !== undefined) addToClass(ix, iz, tileClass); if (terrain !== undefined) placeTerrain(ix, iz, terrain); } } /** + * Creates a smooth, passable path between between (startX, startZ) and (endX, endZ) with the given startWidth and endWidth. + * Paints the given tileclass and terrain. + * + * @property {Vector2D} start - Location of the passage. + * @property {Vector2D} end + * @property {number} startWidth - Size of the passage (perpendicular to the direction of the passage). + * @property {number} endWidth + * @property {number} [startHeight] - Fixed height to be used if the height at the location shouldn't be used. + * @property {number} [endHeight] + * @property {number} smoothWidth - Number of tiles at the passage border to apply height interpolation. + * @property {number} [tileClass] - Marks the passage with this tile class. + * @property {string} [terrain] - Texture to be painted on the passage area. + */ +function createPassage(args) +{ + let bound = x => Math.max(0, Math.min(Math.round(x), getMapSize())); + + let startHeight = args.startHeight !== undefined ? args.startHeight : getHeight(bound(args.start.x), bound(args.start.y)); + let endHeight = args.endHeight !== undefined ? args.endHeight : getHeight(bound(args.end.x), bound(args.end.y)); + + let passageVec = Vector2D.sub(args.end, args.start); + let widthDirection = passageVec.perpendicular().normalize(); + let lengthStep = 1 / (2 * passageVec.length()); + + for (let lengthFraction = 0; lengthFraction <= 1; lengthFraction += lengthStep) + { + let locationLength = Vector2D.add(args.start, Vector2D.mult(passageVec, lengthFraction)); + let halfPassageWidth = (args.startWidth + (args.endWidth - args.startWidth) * lengthFraction) / 2; + let passageHeight = startHeight + (endHeight - startHeight) * lengthFraction; + + for (let stepWidth = -halfPassageWidth; stepWidth <= halfPassageWidth; stepWidth += 0.5) + { + let location = Vector2D.add(locationLength, Vector2D.mult(widthDirection, stepWidth)).round(); + + if (!g_Map.inMapBounds(location.x, location.y)) + continue; + + let smoothDistance = args.smoothWidth + Math.abs(stepWidth) - halfPassageWidth; + + g_Map.setHeight( + location.x, + location.y, + smoothDistance > 0 ? + (getHeight(location.x, location.y) * smoothDistance + passageHeight / smoothDistance) / (smoothDistance + 1 / smoothDistance) : + passageHeight); + + if (args.tileClass) + addToClass(location.x, location.y, args.tileClass); + + if (args.terrain) + placeTerrain(location.x, location.y, args.terrain); + } + } +} + +/** * Creates a ramp from (x1, y1) to (x2, y2). */ function createRamp(x1, y1, x2, y2, minHeight, maxHeight, width, smoothLevel, mainTerrain, edgeTerrain, tileClass) { let halfWidth = width / 2; let x3; let y3; if (y1 == y2) { x3 = x2; y3 = y2 + halfWidth; } else { x3 = x2 + halfWidth; y3 = (x1 - x2) / (y1 - y2) * (x2 - x3) + y2; } let minBoundX = Math.max(Math.min(x1, x2) - halfWidth, 0); let minBoundY = Math.max(Math.min(y1, y2) - halfWidth, 0); let maxBoundX = Math.min(Math.max(x1, x2) + halfWidth, getMapSize()); let maxBoundY = Math.min(Math.max(y1, y2) + halfWidth, getMapSize()); for (let x = minBoundX; x < maxBoundX; ++x) for (let y = minBoundY; y < maxBoundY; ++y) { let lDist = distanceOfPointFromLine(x3, y3, x2, y2, x, y); let sDist = distanceOfPointFromLine(x1, y1, x2, y2, x, y); let rampLength = Math.euclidDistance2D(x1, y1, x2, y2); if (lDist > rampLength || sDist > halfWidth) continue; let height = ((rampLength - lDist) * maxHeight + lDist * minHeight) / rampLength; if (sDist >= halfWidth - smoothLevel) { height = (height - minHeight) * (halfWidth - sDist) / smoothLevel + minHeight; if (edgeTerrain) placeTerrain(x, y, edgeTerrain); } else if (mainTerrain) placeTerrain(x, y, mainTerrain); if (tileClass !== undefined) addToClass(x, y, tileClass); if (getHeight(Math.floor(x), Math.floor(y)) < height && height <= maxHeight) setHeight(x, y, height); } } /** * Get The Intended Point In A Direction Based On Height. * Retrieves the N'th point with a specific height in a line and returns it as a [x, y] array. * * @param startPoint - [x, y] array defining the start point * @param endPoint - [x, y] array defining the ending point * @param heightRange - [min, max] array defining the range which the height of the intended point can be. includes both "min" and "max" * @param step - how much tile units per turn should the search go. more value means faster but less accurate * @param n - how many points to skip before ending the search. skips """n-1 points""". */ function getTIPIADBON(startPoint, endPoint, heightRange, step, n) { let X = endPoint[0] - startPoint[0]; let Y = endPoint[1] - startPoint[1]; if (!X && !Y) { error("getTIPIADBON startPoint and endPoint are identical! " + new Error().stack); return undefined; } let M = Math.sqrt(Math.square(X) + step * Math.square(Y)); let stepX = step * X / M; let stepY = step * Y / M; let y = startPoint[1]; let checked = 0; let mapSize = getMapSize(); for (let x = startPoint[0]; true; x += stepX) { let ix = Math.floor(x); let iy = Math.floor(y); if (ix < mapSize || iy < mapSize) { if (getHeight(ix, iy) <= heightRange[1] && getHeight(ix, iy) >= heightRange[0]) ++checked; if (checked >= n) return [x, y]; } y += stepY; if (y > endPoint[1] && stepY > 0 || y < endPoint[1] && stepY < 0 || x > endPoint[1] && stepX > 0 || x < endPoint[1] && stepX < 0) return undefined; } return undefined; }