Index: binaries/data/mods/public/maps/random/echo_shores.js =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/echo_shores.js @@ -0,0 +1,484 @@ +Engine.LoadLibrary("rmgen"); +Engine.LoadLibrary("rmgen-common"); +Engine.LoadLibrary("rmgen2"); +Engine.LoadLibrary("rmbiome"); + +setSelectedBiome(); + +const elevation = -5; + +let g_Map = new RandomMap(elevation, g_Terrains.mainTerrain); + +const mapSize = g_Map.getSize(); +const mapCenter = g_Map.getCenter(); + +initTileClasses(['expansion', 'resourceOK']); +createArea( + new MapBoundsPlacer(), + new TileClassPainter(g_TileClasses.map)); + +// check if the teams are of even size +const teams = getTeamsArray(); +let evenTeams = true; +const teamLength = teams[0].length; +for (let i = 1; i < teams.length; ++i) +{ + if(teams[i].length != teamLength) + { + evenTeams = false; + break; + } +} + +// determine the valid starting placements +let validPatterns = ["radial"]; + +if (evenTeams && ((mapSize == 320 && getNumPlayers() == 6) || mapSize >= 384)) + validPatterns.push("line"); + +// only allow stronghold placement for 2 teams since it's not perfectly symmetrical +if (evenTeams && teams.length == 2 && getNumPlayers() != 6) + validPatterns.push("stronghold"); + +// limit placement to radial if there's an odd number of players on each team +if (!evenTeams) + validPatterns = ["radial"]; + +// pick a valid starting placement +let pattern = g_MapSettings.TeamPlacement; +if (!validPatterns.includes(pattern)) + pattern = pickRandom(validPatterns); + +const distance = g_PlayerbaseTypes[pattern].distance; +const groupedDistance = g_PlayerbaseTypes[pattern].groupedDistance; + +// when there are an even number of players on each team either divide the map based on the number of players or the number of teams, and always dividing by teams if there's a stronghold or line placement to prevent inaccurately coping the player tile class to other wedges since there are multiple players placed per wedge +let groups = getNumPlayers(); +if (evenTeams) + if(randIntInclusive(0, 1) == 1 || pattern == "stronghold" || pattern == "line") + groups = teams.length; + +// Get the a single player's slice of the map in radians +let playerRadians = 2 * Math.PI / groups; + +// If there is an odd number we halve the slice so we stay symmetrical +if (groups % 2 != 0) + playerRadians /= 2; + +// center the starting angle on the first groups midline +g_Map.startAngle = playerRadians / 2; + +// slightly offset radial teams of 8 so that the bases don't intersect the startAngle +if (getNumPlayers() == 8 && pattern == "radial" && getNumPlayers() != groups) + g_Map.startAngle -= playerRadians / getNumPlayers(); + +// create a circle for placing resources +createArea( + new DiskPlacer(mapSize / 2 * 0.98, mapCenter), + new TileClassPainter(g_TileClasses.resourceOK) +); + +// create a wedge area that will be our reference for copying terrain and entities onto the rest of the map +createArea( + new WedgePlacer(mapCenter, 0, playerRadians), + new TileClassPainter(g_TileClasses.wedge) +); + +// mark the future base locations for resource avoidance +if (!isNomad()) +{ + const baseLocations = g_PlayerbaseTypes[pattern].getPosition(distance, groupedDistance, g_Map.startAngle); + for (let i = 0; i < baseLocations[1].length; ++i) + { + // create a circle for placing base resources + createArea( + new DiskPlacer(20, baseLocations[1][i]), + [ + new SmoothElevationPainter(ELEVATION_SET, 1, 3, 1), + new TileClassPainter(g_TileClasses.island) + ]); + // create a unique shape to give the island some character + createArea( + new ClumpPlacer(800, 0.08, 0.08, Infinity, baseLocations[1][i]), + [ + new SmoothElevationPainter(ELEVATION_SET, 1, 3, 1), + new TileClassPainter(g_TileClasses.island) + ]); + addCivicCenterAreaToClass(baseLocations[1][i], g_TileClasses.player); + } +} +Engine.SetProgress(10); + +let islandAmounts = ["normal"]; +let islandSizes = ["normal"]; +if (mapSize >= 320 && getNumPlayers() <= 4) +{ + islandAmounts.push("many"); + islandSizes.push("big"); +} +if (mapSize <= 256 && getNumPlayers() >= 4) +{ + islandAmounts.push("few"); + islandSizes.push("small"); +} + +let lakeAmounts = ["normal"]; +if (isNomad()) + lakeAmounts = ["few"]; + +addElements([{ + "func": addIslands, + "sizes": islandSizes, + "mixes": g_AllMixes, + "amounts": islandAmounts +}]); +addElements([{ + "func": addLakesWithoutEntities, + "avoid": [ + g_TileClasses.player, 30, + ], + "stay": [ + g_TileClasses.island, 20, + ], + "sizes": ["few", "normal", "big"], + "mixes": g_AllMixes, + "amounts": lakeAmounts +}]); +Engine.SetProgress(20); + +addElements([{ + "func": addHills, + "avoid": [ + g_TileClasses.player, 20, + g_TileClasses.water, 5 + ], + "stay": [ + g_TileClasses.island, 5, + g_TileClasses.wedge, 5 + ], + "sizes": ["tiny"], + "mixes": g_AllMixes, + "amounts": ["normal"] +}]); +Engine.SetProgress(30); + +paintTerrainBasedOnHeight(-100, -1, Elevation_IncludeMin_IncludeMax, g_Terrains.water); +paintTerrainBasedOnHeight(-1, 0, Elevation_IncludeMin_IncludeMax, g_Terrains.shore); +paintTerrainBasedOnHeight(1, 100, Elevation_IncludeMin_IncludeMax, g_Terrains.mainTerrain); +Engine.SetProgress(40); + +addElements(shuffleArray([ + { + "func": addMetal, + "avoid": [ + g_TileClasses.forest, 2, + g_TileClasses.player, 30, + g_TileClasses.rock, 5, + g_TileClasses.metal, 15, + g_TileClasses.water, 4 + ], + "stay": [ + g_TileClasses.island, 4, + g_TileClasses.resourceOK, 0, + g_TileClasses.wedge, 4 + ], + "sizes": ["normal"], + "mixes": ["similar"], + "amounts": ["many", "tons"] + }, + { + "func": addStone, + "avoid": [ + g_TileClasses.forest, 3, + g_TileClasses.player, 30, + g_TileClasses.rock, 15, + g_TileClasses.metal, 4, + g_TileClasses.water, 4 + ], + "stay": [ + g_TileClasses.island, 4, + g_TileClasses.resourceOK, 0, + g_TileClasses.wedge, 4 + ], + "sizes": ["normal"], + "mixes": ["normal"], + "amounts": ["scarce", "few", "normal"] + }, + { + "func": addForests, + "avoid": [ + g_TileClasses.forest, 8, + g_TileClasses.metal, 2, + g_TileClasses.player, 20, + g_TileClasses.rock, 2, + g_TileClasses.water, 5 + ], + "stay": [ + g_TileClasses.island, 5, + g_TileClasses.resourceOK, 0, + g_TileClasses.wedge, 0 + ], + "sizes": ["normal", "big"], + "mixes": g_AllMixes, + "amounts": ["many", "tons"] + } +])); +Engine.SetProgress(50); + +addElements(shuffleArray([ + { + "func": addBerries, + "avoid": [ + g_TileClasses.berries, 15, + g_TileClasses.forest, 3, + g_TileClasses.metal, 5, + g_TileClasses.player, 20, + g_TileClasses.rock, 5, + g_TileClasses.water, 5 + ], + "stay": [ + g_TileClasses.island, 5, + g_TileClasses.resourceOK, 0, + g_TileClasses.wedge, 0 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["normal"] + }, + { + "func": addAnimals, + "avoid": [ + g_TileClasses.animals, 15, + g_TileClasses.forest, 2, + g_TileClasses.metal, 2, + g_TileClasses.player, 20, + g_TileClasses.rock, 2, + g_TileClasses.water, 5 + ], + "stay": [ + g_TileClasses.island, 5, + g_TileClasses.resourceOK, 0, + g_TileClasses.wedge, 0 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["normal"] + }, + { + "func": addStragglerTrees, + "avoid": [ + g_TileClasses.berries, 2, + g_TileClasses.forest, 4, + g_TileClasses.metal, 2, + g_TileClasses.player, 12, + g_TileClasses.rock, 2, + g_TileClasses.water, 5 + ], + "stay": [ + g_TileClasses.island, 5, + g_TileClasses.resourceOK, 0, + g_TileClasses.wedge, 0 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["normal"] + }, + { + "func": addFish, + "avoid": [ + g_TileClasses.fish, 12, + g_TileClasses.island, 9, + g_TileClasses.player, 9 + ], + "stay": [ + g_TileClasses.resourceOK, 0, + g_TileClasses.wedge, 0 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["many", "tons"] + }, + { + "func": addFish, + "avoid": [ + g_TileClasses.fish, 8, + g_TileClasses.player, 9 + ], + "stay": [ + g_TileClasses.resourceOK, 0, + g_TileClasses.water, 6, + g_TileClasses.wedge, 0 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["many", "tons"] + } +])); +Engine.SetProgress(60); + +addElements([ + { + "func": addLayeredPatches, + "avoid": [ + g_TileClasses.player, 12, + g_TileClasses.water, 5 + ], + "stay": [ + g_TileClasses.island, 5, + g_TileClasses.wedge, 0 + ], + "sizes": ["normal"], + "mixes": ["normal"], + "amounts": ["normal"] + }, + { + "func": addDecoration, + "avoid": [ + g_TileClasses.player, 12, + g_TileClasses.water, 5 + ], + "stay": [ + g_TileClasses.island, 5, + g_TileClasses.wedge, 0 + ], + "sizes": ["normal"], + "mixes": ["normal"], + "amounts": ["normal"] + } +]); +Engine.SetProgress(70); + +// create a 2d array of all gaia +let gaiaEntities = [...Array(mapSize)].map(e => Array(mapSize)); +for (let i = 0; i < g_Map.entities.length; ++i) +{ + var ent = g_Map.entities[i]; + if (ent.player != 0) + continue; + + gaiaEntities[Math.floor(ent.position.x)][Math.floor(ent.position.z)] = ent; +} +Engine.SetProgress(80); + +// Cycle through each tile +for (let x = 0; x < mapSize; ++x) +{ + for (let y = 0; y < mapSize; ++y) + { + let point = new Vector2D(x, y); + if(g_Map.validHeight(point)) + { + // Find the angle in radians from the current point to the center of the map + let radians = Math.atan2(y - mapCenter.y, x - mapCenter.x); + + // Check if we're outside of the reference wedge + if (radians < 0 || radians > playerRadians) + { + // determine which direction we're replicating in + let flips = Math.floor(Math.abs(radians) / playerRadians); + let relRadians = playerRadians - Math.abs(radians) % playerRadians; + if (flips % 2 == 0) + relRadians = Math.abs(radians) % playerRadians; + + // find the distance from the current tile to the center + let dist = Math.sqrt(Math.pow((mapCenter.x - x),2) + Math.pow((mapCenter.y - y),2)); + + // match the distance to a similar angle in the reference wedge + let relX = Math.round(dist * Math.cos(relRadians)) + mapCenter.x; + let relY = Math.round(dist * Math.sin(relRadians)) + mapCenter.y; + let relPoint = new Vector2D(relX, relY); + + // copy the contents of the reference tile to the ne tile + if(g_Map.validTile(relPoint) && g_Map.validTilePassable(relPoint) && g_Map.validTile(point) && g_Map.validTilePassable(point)) + { + // set the height, texture, terrain entities and gaia entities based on the reference point + g_Map.setHeight(point, g_Map.getHeight(relPoint)); + g_Map.setTexture(point, g_Map.getTexture(relPoint)); + + let te = g_Map.getTerrainEntity(relPoint); + if (typeof te !== "undefined") + g_Map.setTerrainEntity(te.templateName, te.player, point, te.rotation); + + let ent = gaiaEntities[relX][relY]; + if (ent === undefined) + continue; + + g_Map.placeEntityAnywhere(ent.templateName, ent.player, point, ent.rotation); + } + } + } + } +} +Engine.SetProgress(90); + +// add back the bases +if (!isNomad()) +{ + createBasesByPattern( + pattern, + distance, + groupedDistance, + g_Map.startAngle); + + // If there was an odd number of players, we needed to create an expansion area + // On larger maps with only a few players, fill this empty space with some resources + if (groups % 2 != 0 && (mapSize > 192 || groups < 7)) + { + const expansionLocations = g_PlayerbaseTypes[pattern].getPosition(distance, groupedDistance, g_Map.startAngle + playerRadians); + for (let i = 0; i < expansionLocations[1].length; ++i) + { + const forestType = [ + g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree1, + g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree2, + g_Terrains.forestFloor2 + ]; + + // Paint the expandion area with an expansion class + createArea( + new DiskPlacer(4, expansionLocations[1][i]), + [ + new TileClassPainter(g_TileClasses.expansion) + ], + new NullConstraint()); + + // Add a ring of trees + createArea( + new DiskPlacer(6, expansionLocations[1][i]), + [ + new LayeredPainter(forestType, [2]), + new TileClassPainter(g_TileClasses.forest), + ], + new AvoidTileClassConstraint(g_TileClasses.expansion, 0)); + + // Add some metal in the middle + g_Map.placeEntityAnywhere(g_Gaia.metalLarge, 0, expansionLocations[1][i], 0); + } + } +} + +// Paint water tile class +paintTileClassBasedOnHeight(-50, -1, Elevation_IncludeMin_IncludeMax, g_TileClasses.water); + +// try to place a bonus in the very middle +const bonus = new SimpleGroup([new SimpleObject(g_Gaia.metalLarge, Math.round(groups * 0.4), Math.round(groups * 0.4) + 1, 0, 2)], true, g_TileClasses.metal, mapCenter); +bonus.place(0, + new AndConstraint( + new HeightConstraint(0, 10), + new AvoidTileClassConstraint(g_TileClasses.forest, 8), + new AvoidTileClassConstraint(g_TileClasses.metal, 8), + new AvoidTileClassConstraint(g_TileClasses.rock, 8), + )); + +// place Nomad players +placePlayersNomad( + g_TileClasses.player, + [avoidClasses( + g_TileClasses.animals, 8, + g_TileClasses.berries, 8, + g_TileClasses.forest, 8, + g_TileClasses.metal, 8, + g_TileClasses.rock, 8, + g_TileClasses.water, 8), + stayClasses(g_TileClasses.island, 8)]); + +g_Map.ExportMap(); Index: binaries/data/mods/public/maps/random/echo_shores.json =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/echo_shores.json @@ -0,0 +1,13 @@ +{ + "settings" : { + "Name" : "Echo Shores", + "Script" : "echo_shores.js", + "Description" : "Players battle on a perfectly symmetrical battlefield with water.", + "BaseHeight" : 1, + "Preview" : "echo_shores.png", + "SupportedBiomes": "generic/", + "CircularMap" : true, + "Keywords": ["naval"], + "TeamPlacements": ["radial", "line", "stronghold"] + } +} \ No newline at end of file Index: binaries/data/mods/public/maps/random/echo_valley.js =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/echo_valley.js @@ -0,0 +1,423 @@ +Engine.LoadLibrary("rmgen"); +Engine.LoadLibrary("rmgen-common"); +Engine.LoadLibrary("rmgen2"); +Engine.LoadLibrary("rmbiome"); + +setSelectedBiome(); + +const elevation = 5; + +let g_Map = new RandomMap(elevation, g_Terrains.mainTerrain); + +const mapSize = g_Map.getSize(); +const mapCenter = g_Map.getCenter(); + +initTileClasses(['expansion']); +createArea( + new MapBoundsPlacer(), + new TileClassPainter(g_TileClasses.land)); + +// check if the teams are of even size +const teams = getTeamsArray(); +let evenTeams = true; +const teamLength = teams[0].length; +for (let i = 1; i < teams.length; ++i) +{ + if (teams[i].length != teamLength) + { + evenTeams = false; + break; + } +} + +// determine the valid starting placements +let validPatterns = ["radial"]; + +if (evenTeams && ((mapSize == 320 && getNumPlayers() == 6) || mapSize >= 384)) + validPatterns.push("line"); + +// only allow stronghold placement for 2 teams since it's not perfectly symmetrical +if (evenTeams && teams.length == 2 && getNumPlayers() != 6) + validPatterns.push("stronghold"); + +// limit placement to radial if there's an odd number of players on each team +if (!evenTeams) + validPatterns = ["radial"]; + +// pick a valid starting placement +let pattern = g_MapSettings.TeamPlacement; +if (!validPatterns.includes(pattern)) + pattern = pickRandom(validPatterns); + +const distance = g_PlayerbaseTypes[pattern].distance; +const groupedDistance = g_PlayerbaseTypes[pattern].groupedDistance; + +// either divide the map based on the number of players or number of teams, always dividing by number of players if it's an odd number to prevent terrain colliding with bases, and always dividing by teams if there's a stronghold placement to prevent scarce resources +let groups = getNumPlayers(); +if (groups % 2 == 0) + if (randIntInclusive(0, 1) == 1 || pattern == "stronghold" || pattern == "line") + groups = teams.length; + +// Get the a single player's slice of the map in radians +let playerRadians = 2 * Math.PI / groups; + +// If there is an odd number halve the wedge to ensure symmetry +if (groups % 2 != 0) + playerRadians /= 2; + +// center the starting angle on the first groups midline +g_Map.startAngle = playerRadians / 2; + +// slightly offset radial teams of 8 so that the bases don't intersect the startAngle +if (getNumPlayers() == 8 && pattern == "radial" && getNumPlayers() != groups) + g_Map.startAngle -= playerRadians / getNumPlayers(); + +// create a wedge area that will be our reference for copying terrain and entities onto the rest of the map +createArea( + new WedgePlacer(mapCenter, 0, playerRadians), + new TileClassPainter(g_TileClasses.wedge) +); + +// mark the future base locations for resource avoidance +const baseLocations = g_PlayerbaseTypes[pattern].getPosition(distance, groupedDistance, g_Map.startAngle); +if (!isNomad()) +{ + for (let i = 0; i < baseLocations[1].length; ++i) + addCivicCenterAreaToClass(baseLocations[1][i], g_TileClasses.player); +} +Engine.SetProgress(20); + +var features = [ + { + "func": addHills, + "avoid": [ + g_TileClasses.hill, 15, + g_TileClasses.mountain, 2, + g_TileClasses.plateau, 15, + g_TileClasses.player, 20 + ], + "stay": [ + g_TileClasses.wedge, 6 + ], + "sizes": ["tiny"], + "mixes": g_AllMixes, + "amounts": ["normal"] + }, + { + "func": addMountains, + "avoid": [ + g_TileClasses.hill, 5, + g_TileClasses.mountain, 15, + g_TileClasses.plateau, 15, + g_TileClasses.player, 20 + ], + "stay": [ + g_TileClasses.wedge, 6 + ], + "sizes": ["tiny"], + "mixes": ["normal"], + "amounts": ["normal", "many"] + }, + { + "func": addPlateaus, + "avoid": [ + g_TileClasses.hill, 5, + g_TileClasses.mountain, 15, + g_TileClasses.plateau, 15, + g_TileClasses.player, 20 + ], + "stay": [ + g_TileClasses.wedge, 6 + ], + "sizes": ["tiny"], + "mixes": ["normal"], + "amounts": ["normal", "many"] + } +]; + +addElements(shuffleArray(features)); +Engine.SetProgress(30); + +addElements([ + { + "func": addLayeredPatches, + "avoid": [ + g_TileClasses.dirt, 5, + g_TileClasses.forest, 2, + g_TileClasses.mountain, 2, + g_TileClasses.plateau, 2, + g_TileClasses.player, 12 + ], + "stay": [ + g_TileClasses.wedge, 0 + ], + "sizes": ["normal"], + "mixes": ["normal"], + "amounts": ["normal"] + }, + { + "func": addDecoration, + "avoid": [ + g_TileClasses.forest, 1, + g_TileClasses.mountain, 1, + g_TileClasses.plateau, 1, + g_TileClasses.player, 12 + ], + "stay": [ + g_TileClasses.wedge, 0 + ], + "sizes": ["normal"], + "mixes": ["normal"], + "amounts": ["normal"] + } +]); +Engine.SetProgress(40); + +addElements(shuffleArray([ + { + "func": addMetal, + "avoid": [ + g_TileClasses.berries, 3, + g_TileClasses.forest, 2, + g_TileClasses.mountain, 3, + g_TileClasses.plateau, 3, + g_TileClasses.player, 30, + g_TileClasses.rock, 5, + g_TileClasses.metal, 15 + ], + "stay": [ + g_TileClasses.wedge, 4 + ], + "sizes": ["normal"], + "mixes": ["similar"], + "amounts": ["normal", "many"] + }, + { + "func": addStone, + "avoid": [ + g_TileClasses.berries, 3, + g_TileClasses.forest, 3, + g_TileClasses.mountain, 4, + g_TileClasses.plateau, 4, + g_TileClasses.player, 30, + g_TileClasses.rock, 15, + g_TileClasses.metal, 5 + ], + "stay": [ + g_TileClasses.wedge, 4 + ], + "sizes": ["normal"], + "mixes": ["normal"], + "amounts": ["scarce", "few", "normal"] + }, + { + "func": addForests, + "avoid": [ + g_TileClasses.berries, 3, + g_TileClasses.forest, 15, + g_TileClasses.metal, 2, + g_TileClasses.mountain, 4, + g_TileClasses.plateau, 4, + g_TileClasses.player, 20, + g_TileClasses.rock, 2 + ], + "stay": [ + g_TileClasses.wedge, 0 + ], + "sizes": ["normal", "big"], + "mixes": g_AllMixes, + "amounts": ["normal", "many"] + } +])); +Engine.SetProgress(50); + +addElements(shuffleArray([ + { + "func": addBerries, + "avoid": [ + g_TileClasses.berries, 15, + g_TileClasses.forest, 3, + g_TileClasses.metal, 5, + g_TileClasses.mountain, 4, + g_TileClasses.plateau, 4, + g_TileClasses.player, 20, + g_TileClasses.rock, 5 + ], + "stay": [ + g_TileClasses.wedge, 4 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["normal"] + }, + { + "func": addAnimals, + "avoid": [ + g_TileClasses.animals, 15, + g_TileClasses.forest, 2, + g_TileClasses.metal, 2, + g_TileClasses.mountain, 4, + g_TileClasses.plateau, 4, + g_TileClasses.player, 20, + g_TileClasses.rock, 2 + ], + "stay": [ + g_TileClasses.wedge, 4 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["normal"] + }, + { + "func": addStragglerTrees, + "avoid": [ + g_TileClasses.berries, 2, + g_TileClasses.forest, 4, + g_TileClasses.metal, 2, + g_TileClasses.mountain, 2, + g_TileClasses.plateau, 2, + g_TileClasses.player, 12, + g_TileClasses.rock, 2 + ], + "stay": [ + g_TileClasses.wedge, 0 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["normal"] + } +])); +Engine.SetProgress(60); + +// create a 2d array of all gaia +let gaiaEntities = [...Array(mapSize)].map(e => Array(mapSize)); +for (let i = 0; i < g_Map.entities.length; ++i) +{ + let ent = g_Map.entities[i]; + if (ent.player != 0) + continue; + + gaiaEntities[Math.floor(ent.position.x)][Math.floor(ent.position.z)] = ent; +} + +// Cycle through each tile +for (let x = 0; x < mapSize; ++x) +{ + for (let y = 0; y < mapSize; ++y) + { + let point = new Vector2D(x, y); + if (g_Map.validHeight(point)) + { + // Find the angle in radians from the current point to the center of the map + let radians = Math.atan2(y - mapCenter.y, x - mapCenter.x); + + // Check if we're outside of the reference wedge + if (radians < 0 || radians > playerRadians) + { + // determine which direction we're replicating in + let flips = Math.floor(Math.abs(radians) / playerRadians); + let relRadians = playerRadians - Math.abs(radians) % playerRadians; + if (flips % 2 == 0) + relRadians = Math.abs(radians) % playerRadians; + + // find the distance from the current tile to the center + let dist = Math.sqrt(Math.pow((mapCenter.x - x), 2) + Math.pow((mapCenter.y - y), 2)); + + // match the distance to a similar angle in the reference wedge + let relX = Math.round(dist * Math.cos(relRadians)) + mapCenter.x; + let relY = Math.round(dist * Math.sin(relRadians)) + mapCenter.y; + let relPoint = new Vector2D(relX, relY); + + // copy the contents of the reference tile to the ne tile + if (g_Map.validTile(relPoint) && g_Map.validTilePassable(relPoint) && g_Map.validTile(point) && g_Map.validTilePassable(point)) + { + // set the height, texture, terrain entities and gaia entities based on the reference point + g_Map.setHeight(point, g_Map.getHeight(relPoint)); + g_Map.setTexture(point, g_Map.getTexture(relPoint)); + + let te = g_Map.getTerrainEntity(relPoint); + if (typeof te !== "undefined") + g_Map.setTerrainEntity(te.templateName, te.player, point, te.rotation); + + let ent = gaiaEntities[relX][relY]; + if (ent === undefined) + continue; + + g_Map.placeEntityAnywhere(ent.templateName, ent.player, point, ent.rotation); + } + } + } + } +} + +Engine.SetProgress(90); + +// add back the bases +if (!isNomad()) +{ + createBasesByPattern( + pattern, + distance, + groupedDistance, + g_Map.startAngle); + + // If there was an odd number of players, we needed to create an expansion area + // On larger maps with only a few players, fill this empty space with some resources + if (groups % 2 != 0 && (mapSize > 192 || groups < 7)) + { + const expansionLocations = g_PlayerbaseTypes[pattern].getPosition(distance, groupedDistance, g_Map.startAngle + playerRadians); + for (let i = 0; i < expansionLocations[1].length; ++i) + { + const forestType = [ + g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree1, + g_Terrains.forestFloor2 + TERRAIN_SEPARATOR + g_Gaia.tree2, + g_Terrains.forestFloor2 + ]; + + // Paint the expandion area with an expansion class + createArea( + new DiskPlacer(4, expansionLocations[1][i]), + [ + new TileClassPainter(g_TileClasses.expansion) + ], + new NullConstraint()); + + // Add a ring of trees + createArea( + new DiskPlacer(6, expansionLocations[1][i]), + [ + new LayeredPainter(forestType, [2]), + new TileClassPainter(g_TileClasses.forest), + ], + new AvoidTileClassConstraint(g_TileClasses.expansion, 0)); + + // Add some metal in the middle + g_Map.placeEntityAnywhere(g_Gaia.metalLarge, 0, expansionLocations[1][i], 0); + } + } +} + +// try to place a bonus in the very middle +const bonus = new SimpleGroup([new SimpleObject(g_Gaia.metalLarge, Math.round(groups * 0.4), Math.round(groups * 0.4) + 1, 0, 2)], true, g_TileClasses.metal, mapCenter); +bonus.place(0, + new AndConstraint( + new HeightConstraint(0, 10), + new AvoidTileClassConstraint(g_TileClasses.forest, 8), + new AvoidTileClassConstraint(g_TileClasses.metal, 8), + new AvoidTileClassConstraint(g_TileClasses.rock, 8), + )); + +// place Nomad players +placePlayersNomad( + g_TileClasses.player, + avoidClasses( + g_TileClasses.bluff, 4, + g_TileClasses.water, 4, + g_TileClasses.forest, 1, + g_TileClasses.metal, 4, + g_TileClasses.rock, 4, + g_TileClasses.mountain, 4, + g_TileClasses.plateau, 4, + g_TileClasses.animals, 2)); + +g_Map.ExportMap(); Index: binaries/data/mods/public/maps/random/echo_valley.json =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/echo_valley.json @@ -0,0 +1,12 @@ +{ + "settings" : { + "Name" : "Echo Valley", + "Script" : "echo_valley.js", + "Description" : "Players battle on a perfectly symmetrical battlefield.", + "BaseHeight" : 1, + "Preview" : "echo-valley.png", + "SupportedBiomes": "generic/", + "CircularMap" : true, + "TeamPlacements": ["radial", "line", "stronghold"] + } +} \ No newline at end of file Index: binaries/data/mods/public/maps/random/rmgen/placer/noncentered/WedgePlacer.js =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/rmgen/placer/noncentered/WedgePlacer.js @@ -0,0 +1,47 @@ +/** + * The WedgePlacer returns all tiles between a center point and two angles. + */ + +function WedgePlacer(centerPoint, startAngle, stopAngle, failFraction = 100) { + this.normalize = 0; + this.centerPoint = centerPoint; + this.startAngle = startAngle; + this.stopAngle = stopAngle; + + // add 2 * Math.PI to ensure a positive angle + if (this.startAngle < 0 || this.stopAngle < 0) { + this.normalize = 2 * Math.PI; + this.startAngle = startAngle + this.normalize; + this.stopAngle = stopAngle + this.normalize; + } + + this.failFraction = failFraction; +} + +WedgePlacer.prototype.place = function (constraint) { + let points = []; + let count = 0; + let failed = 0; + + for (let x = 0; x < g_Map.getSize(); ++x) { + for (let y = 0; y < g_Map.getSize(); ++y) { + ++count; + + let point = new Vector2D(x, y); + let radians = Math.atan2(point.y - this.centerPoint.y, point.x - this.centerPoint.x) + this.normalize; + + if (radians < 0) { + radians += 2 * Math.PI; + } + + // check if this angle is between the two angles (plus an optional full rotation) + if (g_Map.inMapBounds(point) && ((radians >= this.startAngle && radians < this.stopAngle) || (radians + 2 * Math.PI >= this.startAngle && radians + 2 * Math.PI < this.stopAngle)) && constraint.allows(point)) { + points.push(point); + } else { + ++failed; + } + } + } + + return failed <= this.failFraction * count ? points : undefined; +} Index: binaries/data/mods/public/maps/random/rmgen2/gaia.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen2/gaia.js +++ binaries/data/mods/public/maps/random/rmgen2/gaia.js @@ -429,7 +429,7 @@ var spread = el.spread; var elType = ELEVATION_MODIFY; - if (el.class == g_TileClasses.water) + if (el.class == g_TileClasses.water || g_TileClasses.island) elType = ELEVATION_SET; var widths = []; @@ -503,6 +503,38 @@ { g_Map.log("Creating lakes"); + addLakesWithoutEntities(constraint, size, deviation, fill); + + 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); +} + +/** + * Create random lakes with fish in it. + */ +function addLakesWithoutEntities(constraint, size, deviation, fill) +{ + g_Map.log("Creating plain lakes"); + var lakeTile = g_Terrains.water; if (currentBiome() == "generic/temperate" || currentBiome() == "generic/india") @@ -528,28 +560,29 @@ "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); +/** + * Create islands + */ +function addIslands(constraint, size, deviation, fill) +{ + g_Map.log("Creating islands"); - 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); + addElevation(constraint, { + "class": g_TileClasses.island, + "painter": [g_Terrains.shore, g_Terrains.mainTerrain], + "size": size, + "deviation": deviation, + "fill": fill, + "count": 6, + "minSize": 7, + "maxSize": 9, + "spread": 70, + "minElevation": 1, + "maxElevation": 3, + "steepness": 1 + }); } /** Index: binaries/data/mods/public/maps/random/rmgen2/setup.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen2/setup.js +++ binaries/data/mods/public/maps/random/rmgen2/setup.js @@ -37,6 +37,7 @@ "food", "forest", "hill", + "island", "land", "map", "metal", @@ -49,7 +50,8 @@ "settlement", "spine", "valley", - "water" + "water", + "wedge" ]; var g_TileClasses;