Index: binaries/data/mods/public/maps/random/echo_valley.js =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/echo_valley.js @@ -0,0 +1,410 @@ +Engine.LoadLibrary("rmgen"); +Engine.LoadLibrary("rmgen-common"); +Engine.LoadLibrary("rmgen2"); +Engine.LoadLibrary("rmbiome"); + +setSelectedBiome(); + +var elevation = 25; + +var g_Map = new RandomMap(elevation, g_Terrains.mainTerrain); + +initTileClasses(); +createArea( + new MapBoundsPlacer(), + new TileClassPainter(g_TileClasses.land)); + +// check if the teams are of even size +let teams = getTeamsArray(); +let evenTeams = true; +let 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", "line"]; +// only allow stronghold placement for 2 teams since it's not perfectly symmetrical +if (evenTeams && teams.length == 2) + 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); + +let distance = g_PlayerbaseTypes[pattern].distance; +let groupedDistance = g_PlayerbaseTypes[pattern].groupedDistance; + +var mapSize = g_Map.getSize(); +var mapCenter = g_Map.getCenter(); + +// 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") + groups = teams.length; + +// Get the a single player's slice of the map in radians +var playerRadians = 2 * Math.PI / groups; + +// If there is an odd number we halve the slice so we stay symmetrical +if (groups % 2 != 0) + playerRadians = playerRadians / 2; + +// center the starting angle on the first groups midline +g_Map.startAngle = playerRadians / 2; + +// 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()) +{ + let baseLocations = g_PlayerbaseTypes[pattern].getPosition(distance, groupedDistance, g_Map.startAngle); + 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.bluff, 5, + g_TileClasses.hill, 15, + g_TileClasses.mountain, 2, + g_TileClasses.plateau, 15, + g_TileClasses.player, 20, + g_TileClasses.valley, 2, + g_TileClasses.water, 2 + ], + "stay": [ + g_TileClasses.wedge, 2 + ], + "sizes": ["tiny"], + "mixes": g_AllMixes, + "amounts": ["normal"] + }, + { + "func": addMountains, + "avoid": [ + g_TileClasses.bluff, 15, + g_TileClasses.hill, 5, + g_TileClasses.mountain, 15, + g_TileClasses.plateau, 15, + g_TileClasses.player, 20, + g_TileClasses.valley, 5, + g_TileClasses.water, 2 + ], + "stay": [ + g_TileClasses.wedge, 6 + ], + "sizes": ["tiny"], + "mixes": g_AllMixes, + "amounts": ["normal"] + }, + { + "func": addPlateaus, + "avoid": [ + g_TileClasses.bluff, 15, + g_TileClasses.hill, 5, + g_TileClasses.mountain, 15, + g_TileClasses.plateau, 15, + g_TileClasses.player, 20, + g_TileClasses.valley, 5, + g_TileClasses.water, 2 + ], + "stay": [ + g_TileClasses.wedge, 8 + ], + "sizes": ["tiny"], + "mixes": ["normal"], + "amounts": ["normal"] + } +]; + +addElements(shuffleArray(features)); +Engine.SetProgress(30); + +addElements([ + { + "func": addLayeredPatches, + "avoid": [ + g_TileClasses.bluff, 2, + g_TileClasses.dirt, 5, + g_TileClasses.forest, 2, + g_TileClasses.mountain, 2, + g_TileClasses.plateau, 2, + g_TileClasses.player, 12, + g_TileClasses.water, 2 + ], + "stay": [ + g_TileClasses.wedge, 1 + ], + "sizes": ["normal"], + "mixes": ["normal"], + "amounts": ["normal"] + }, + { + "func": addDecoration, + "avoid": [ + g_TileClasses.bluff, 1, + g_TileClasses.forest, 1, + g_TileClasses.mountain, 1, + g_TileClasses.plateau, 1, + g_TileClasses.player, 12, + g_TileClasses.water, 1 + ], + "stay": [ + g_TileClasses.wedge, 1 + ], + "sizes": ["normal"], + "mixes": ["normal"], + "amounts": ["normal"] + } +]); +Engine.SetProgress(40); + +addElements(shuffleArray([ + { + "func": addMetal, + "avoid": [ + g_TileClasses.berries, 3, + g_TileClasses.bluff, 2, + g_TileClasses.forest, 2, + g_TileClasses.mountain, 3, + g_TileClasses.plateau, 3, + g_TileClasses.player, 30, + g_TileClasses.rock, 5, + g_TileClasses.metal, 15, + g_TileClasses.water, 2 + ], + "stay": [ + g_TileClasses.wedge, 1 + ], + "sizes": ["normal"], + "mixes": ["similar"], + "amounts": ["normal", "many"] + }, + { + "func": addStone, + "avoid": [ + g_TileClasses.berries, 3, + g_TileClasses.bluff, 2, + g_TileClasses.forest, 3, + g_TileClasses.mountain, 4, + g_TileClasses.plateau, 4, + g_TileClasses.player, 30, + g_TileClasses.rock, 15, + g_TileClasses.metal, 5, + g_TileClasses.water, 2 + ], + "stay": [ + g_TileClasses.wedge, 1 + ], + "sizes": ["normal"], + "mixes": ["normal"], + "amounts": ["scarce", "few", "normal"] + }, + { + "func": addForests, + "avoid": [ + g_TileClasses.berries, 3, + g_TileClasses.bluff, 2, + g_TileClasses.forest, 15, + g_TileClasses.metal, 2, + g_TileClasses.mountain, 4, + g_TileClasses.plateau, 4, + g_TileClasses.player, 20, + g_TileClasses.rock, 2, + g_TileClasses.water, 2 + ], + "stay": [ + g_TileClasses.wedge, 1 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["normal", "many"] + } +])); +Engine.SetProgress(50); + +addElements(shuffleArray([ + { + "func": addBerries, + "avoid": [ + g_TileClasses.berries, 15, + g_TileClasses.bluff, 2, + g_TileClasses.forest, 3, + g_TileClasses.metal, 5, + g_TileClasses.mountain, 4, + g_TileClasses.plateau, 4, + g_TileClasses.player, 20, + g_TileClasses.rock, 5, + g_TileClasses.water, 2 + ], + "stay": [ + g_TileClasses.wedge, 1 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["normal"] + }, + { + "func": addAnimals, + "avoid": [ + g_TileClasses.animals, 15, + g_TileClasses.bluff, 2, + g_TileClasses.forest, 2, + g_TileClasses.metal, 2, + g_TileClasses.mountain, 4, + g_TileClasses.plateau, 4, + g_TileClasses.player, 20, + g_TileClasses.rock, 2, + g_TileClasses.water, 2 + ], + "stay": [ + g_TileClasses.wedge, 1 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["normal"] + }, + { + "func": addStragglerTrees, + "avoid": [ + g_TileClasses.berries, 2, + g_TileClasses.bluff, 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, + g_TileClasses.water, 2 + ], + "stay": [ + g_TileClasses.wedge, 1 + ], + "sizes": ["normal"], + "mixes": g_AllMixes, + "amounts": ["normal"] + } +])); +Engine.SetProgress(60); + +// create a 2d array of all gaia +var 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; +} + +// Cycle through each tile +for (let x = 0; x < mapSize; ++x) +{ + for (let y = 0; y < mapSize; ++y) + { + var 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); + var 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)); + + var te = g_Map.getTerrainEntity(relPoint); + if (typeof te !== "undefined") + g_Map.setTerrainEntity(te.templateName, te.player, point, te.rotation); + + var ent = gaiaEntities[relX][relY]; + if (ent === undefined) + continue; + + g_Map.placeEntityAnywhere(ent.templateName, ent.player, point, ent.rotation); + } + + // set the terrain texture and trees to that of the reference slice + // we don't need to copy classes because we're adding objects directly to g_Map + /*if(g_Map.validTile(point, 2)) + { + let texture = g_Map.getTerrainEntity(relPoint); + g_Map.setTerrainEntity(texture, 0, point); + + if (g_Map.terrainObjects[relX][relZ] !== undefined) + g_Map.addObject(new Entity(g_Map.terrainObjects[relX][relZ].templateName, 0, x, y, 0)); + }*/ + + // if there was an entity (metal, stone, decoration, animal, etc.), replicate it + //let key = relX + "," + relZ; + //if (templateObjects[key] != undefined) + // g_Map.addObject(new Entity(templateObjects[key].templateName, templateObjects[key].player, x, z, templateObjects[key].rotation.y)); + } + } + } +} + +Engine.SetProgress(90); + +// add back the bases +if (!isNomad()) +{ + createBasesByPattern( + pattern, + distance, + groupedDistance, + g_Map.startAngle); +} + +// 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(); \ No newline at end of file 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/setup.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen2/setup.js +++ binaries/data/mods/public/maps/random/rmgen2/setup.js @@ -49,7 +49,8 @@ "settlement", "spine", "valley", - "water" + "water", + "wedge" ]; var g_TileClasses;