Index: binaries/data/mods/public/maps/random/african_plains_tg.js =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/african_plains_tg.js @@ -0,0 +1,315 @@ +Engine.LoadLibrary("rmgen"); +Engine.LoadLibrary("rmgen-common"); +Engine.LoadLibrary("rmbiome"); + +if (g_MapSettings.Biome) + setSelectedBiome(); +else + setBiome("generic/savanna"); + + +// Pick some biome defaults and overload a few settings. +var tPrimary = g_Terrains.mainTerrain; +var tForestFloor = g_Terrains.forestFloor1; +var tCliff = ["savanna_cliff_a", "savanna_cliff_a_red", "savanna_cliff_b", "savanna_cliff_b_red"]; +var tSecondary = g_Terrains.tier4Terrain; +var tGrassShrubs = g_Terrains.tier1Terrain; +var tDirt = g_Terrains.dirt; +var tDirt2 = "savanna_dirt_a_red"; +var tDirt3 = g_Terrains.dirt; +var tDirt4 = g_Terrains.hill; +var tCitytiles = "savanna_tile_a"; +var tShore = g_Terrains.shore; +var tWater = g_Terrains.water; + +var oBaobab = g_Gaia.tree1; +var oPalm = g_Gaia.tree2; +var oPalm2 = g_Gaia.tree3; +var oBerryBush = "gaia/fruit/berry_01"; +var oWildebeest = "gaia/fauna_wildebeest"; +var oZebra = "gaia/fauna_zebra"; +var oRhino = "gaia/fauna_rhinoceros_white"; +var oLion = "gaia/fauna_lion"; +var oLioness = "gaia/fauna_lioness"; +var oHawk = "birds/buzzard"; +var oGiraffe = "gaia/fauna_giraffe"; +var oGiraffe2 = "gaia/fauna_giraffe_infant"; +var oGazelle = "gaia/fauna_gazelle"; +var oElephant = "gaia/fauna_elephant_african_bush"; +var oElephant2 = "gaia/fauna_elephant_african_infant"; +var oCrocodile = "gaia/fauna_crocodile_nile"; +var oFish = g_Gaia.fish; +var oStoneLarge = g_Gaia.stoneLarge; +var oStoneSmall = g_Gaia.stoneSmall; +var oMetalLarge = g_Gaia.metalLarge; +var oMetalSmall = g_Gaia.metalSmall; + +var aBush = g_Decoratives.bushMedium; +var aRock = g_Decoratives.rockMedium; + +const pForest = [tForestFloor + TERRAIN_SEPARATOR + oPalm, tForestFloor + TERRAIN_SEPARATOR + oPalm2, tForestFloor]; + +var heightSeaGround = -5; +var heightLand = 2; +var heightCliff = 3; + +var g_Map = new RandomMap(heightLand, tPrimary); + +const numPlayers = getNumPlayers(); +const mapSize = g_Map.getSize(); + +var clPlayer = g_Map.createTileClass(); +var clHill = g_Map.createTileClass(); +var clForest = g_Map.createTileClass(); +var clWater = g_Map.createTileClass(); +var clDirt = g_Map.createTileClass(); +var clRock = g_Map.createTileClass(); +var clMetal = g_Map.createTileClass(); +var clFood = g_Map.createTileClass(); +var clBaseResource = g_Map.createTileClass(); + +const mapCenter = g_Map.getCenter(); +var playerIDs = sortAllPlayers(); + +placePlayerBases({ + "PlayerPlacement": [sortAllPlayers(), ...playerPlacementMultiArcs( + playerIDs, + mapCenter, + fractionToTiles(0.35), + 0, + 0.75)], + "PlayerTileClass": clPlayer, + "BaseResourceClass": clBaseResource, + "CityPatch": { + "outerTerrain": tPrimary, + "innerTerrain": tCitytiles + }, + "Chicken": { + }, + "Berries": { + "template": oBerryBush + }, + "Mines": { + "types": [ + { + "template": oMetalLarge + }, + { + "type": "stone_formation", + "template": oStoneSmall, + "terrain": tDirt4 + } + ] + }, + "Trees": { + "template": oBaobab, + "count": scaleByMapSize(3, 12), + "minDistGroup": 2, + "maxDistGroup": 6, + "minDist": 15, + "maxDist": 16 + } + // No decoratives +}); +Engine.SetProgress(20); + +// The specificity of this map is a bunch of watering holes & hills making it somewhat cluttered. +const nbHills = scaleByMapSize(6, 16); +const nbWateringHoles = scaleByMapSize(4, 10); +{ + g_Map.log("Creating hills"); + createHills([tDirt2, tCliff, tGrassShrubs], avoidClasses(clPlayer, 30, clHill, 15), clHill, nbHills); + Engine.SetProgress(30); + + g_Map.log("Creating water holes"); + createAreas( + new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), Math.floor(scaleByMapSize(60, 100)), Infinity), + [ + new LayeredPainter([tShore, tWater], [1]), + new SmoothElevationPainter(ELEVATION_SET, heightSeaGround, 7), + new TileClassPainter(clWater) + ], + avoidClasses(clPlayer, 22, clWater, 8, clHill, 2), + nbWateringHoles); + + Engine.SetProgress(45); + + paintTerrainBasedOnHeight(heightCliff, Infinity, 0, tCliff); +} + +createBumps(avoidClasses(clWater, 2, clPlayer, 20)); + +g_Map.log("Creating forests"); + +createDefaultForests( + [tPrimary, pForest, tForestFloor, pForest, pForest], + avoidClasses(clPlayer, 20, clForest, 15, clHill, 0, clWater, 2), + clForest, + scaleByMapSize(500, 3000)); +Engine.SetProgress(50); + +g_Map.log("Creating dirt patches"); +createLayeredPatches( + [scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)], + [[tDirt, tDirt3], [tDirt2, tDirt4]], + [2], + avoidClasses(clWater, 3, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12), + scaleByMapSize(15, 45), + clDirt); +Engine.SetProgress(55); + +g_Map.log("Creating shrubs"); +createPatches( + [scaleByMapSize(2, 4), scaleByMapSize(3, 7), scaleByMapSize(5, 15)], + tGrassShrubs, + avoidClasses(clWater, 3, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12), + scaleByMapSize(15, 45), + clDirt); +Engine.SetProgress(60); + +g_Map.log("Creating grass patches"); +createPatches( + [scaleByMapSize(2, 4), scaleByMapSize(3, 7), scaleByMapSize(5, 15)], + tSecondary, + avoidClasses(clWater, 3, clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12), + scaleByMapSize(15, 45), + clDirt); +Engine.SetProgress(65); + +g_Map.log("Creating metal mines"); +createBalancedMetalMines( + oMetalSmall, + oMetalLarge, + clMetal, + avoidClasses(clWater, 4, clForest, 1, clPlayer, scaleByMapSize(20, 35), clHill, 1) +); + +g_Map.log("Creating stone mines"); +createBalancedStoneMines( + oStoneSmall, + oStoneLarge, + clRock, + avoidClasses(clWater, 4, clForest, 1, clPlayer, scaleByMapSize(20, 35), clHill, 1, clMetal, 10) +); + +Engine.SetProgress(70); + +createDecoration( + [ + [new SimpleObject(aBush, 1,3, 0,1)], + [new SimpleObject(aRock, 1,2, 0,1)] + ], + [ + scaleByMapAreaAbsolute(8), + scaleByMapAreaAbsolute(8) + ], + avoidClasses(clWater, 0, clForest, 0, clPlayer, 0, clHill, 0)); +Engine.SetProgress(75); + +// Roaming animals +{ + var placeRoaming = function(name, objs) + { + g_Map.log("Creating roaming " + name); + const group = new SimpleGroup(objs, true, clFood); + createObjectGroups(group, 0, + avoidClasses(clWater, 3, clPlayer, 20, clFood, 11, clHill, 4), + scaleByMapSize(3, 9), 50 + ); + }; + + placeRoaming("giraffes", [new SimpleObject(oGiraffe, 2, 4, 0, 4), new SimpleObject(oGiraffe2, 0, 2, 0, 4)]); + placeRoaming("elephants", [new SimpleObject(oElephant, 2, 4, 0, 4), new SimpleObject(oElephant2, 0, 2, 0, 4)]); + placeRoaming("lions", [new SimpleObject(oLion, 0, 1, 0, 4), new SimpleObject(oLioness, 2, 3, 0, 4)]); + + // Other roaming animals + createFood( + [ + [new SimpleObject(oHawk, 1, 1, 0, 3)], + [new SimpleObject(oGazelle, 3, 5, 0, 3)], + [new SimpleObject(oZebra, 3, 5, 0, 3)], + [new SimpleObject(oWildebeest, 4, 6, 0, 3)], + [new SimpleObject(oRhino, 1, 1, 0, 3)] + ], + [ + 3 * numPlayers, + 3 * numPlayers, + 3 * numPlayers, + 3 * numPlayers, + 3 * numPlayers, + ], + avoidClasses(clFood, 20, clWater, 5, clHill, 2, clPlayer, 16), + clFood); +} + +// Animals that hang around watering holes +{ + // TODO: these have a high retry factor because our mapgen constraint logic is bad. + var placeWateringHoleAnimals = function(name, objs, numberOfGroups) + { + g_Map.log("Creating " + name + " around watering holes"); + const group = new SimpleGroup(objs, true, clFood); + createObjectGroups(group, 0, + borderClasses(clWater, 6, 3), + numberOfGroups, 50 + ); + }; + placeWateringHoleAnimals( + "crocodiles", + [new SimpleObject(oCrocodile, 2, 3, 0, 3)], + nbWateringHoles * 0.8 + ); + placeWateringHoleAnimals( + "zebras", + [new SimpleObject(oZebra, 2, 5, 0, 3)], + nbWateringHoles + ); + placeWateringHoleAnimals( + "gazelles", + [new SimpleObject(oGazelle, 2, 5, 0, 3)], + nbWateringHoles + ); +} + + +g_Map.log("Creating other food sources"); +createFood( + [ + [new SimpleObject(oBerryBush, 5, 7, 0, 4)] + ], + [ + randIntInclusive(1, 4) * numPlayers + 2 + ], + avoidClasses(clWater, 3, clForest, 2, clPlayer, 20, clHill, 3, clFood, 10), + clFood); + +createFood( + [ + [new SimpleObject(oFish, 2, 3, 0, 2)] + ], + [ + 15 * numPlayers + ], + [avoidClasses(clFood, 20), stayClasses(clWater, 6)], + clFood); +Engine.SetProgress(85); + + +g_Map.log("Creating straggler baobabs"); +const group = new SimpleGroup([new SimpleObject(oBaobab, 1, 3, 0, 3)], true, clForest); +createObjectGroups( + group, + 0, + avoidClasses(clWater, 0, clForest, 2, clHill, 3, clPlayer, 12, clMetal, 4, clRock, 4), + scaleByMapSize(15, 75) +); + + +placePlayersNomad(clPlayer, avoidClasses(clWater, 4, clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2)); + +// Adjust some biome settings; + +setSkySet("sunny"); +setWaterType("clap"); + +g_Map.ExportMap(); Index: binaries/data/mods/public/maps/random/african_plains_tg.json =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/african_plains_tg.json @@ -0,0 +1,10 @@ +{ + "settings" : { + "Name" : "African Plains TG", + "Script" : "african_plains_tg.js", + "Description" : "The central region of the vast continent of Africa, birthplace of humanity. Players start in a lush area teeming with vegetation and wildlife; teammates are placed more closely together.", + "SupportedBiomes": ["generic/savanna", "generic/sahara", "generic/nubia"], + "Preview" : "african_plains.png", + "CircularMap" : true + } +} Index: binaries/data/mods/public/maps/random/mainland_tg.js =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/mainland_tg.js @@ -0,0 +1,204 @@ +Engine.LoadLibrary("rmgen"); +Engine.LoadLibrary("rmgen-common"); +Engine.LoadLibrary("rmbiome"); + +setSelectedBiome(); + +const tMainTerrain = g_Terrains.mainTerrain; +const tForestFloor1 = g_Terrains.forestFloor1; +const tForestFloor2 = g_Terrains.forestFloor2; +const tCliff = g_Terrains.cliff; +const tTier1Terrain = g_Terrains.tier1Terrain; +const tTier2Terrain = g_Terrains.tier2Terrain; +const tTier3Terrain = g_Terrains.tier3Terrain; +const tHill = g_Terrains.hill; +const tRoad = g_Terrains.road; +const tRoadWild = g_Terrains.roadWild; +const tTier4Terrain = g_Terrains.tier4Terrain; + +const oTree1 = g_Gaia.tree1; +const oTree2 = g_Gaia.tree2; +const oTree3 = g_Gaia.tree3; +const oTree4 = g_Gaia.tree4; +const oTree5 = g_Gaia.tree5; +const oFruitBush = g_Gaia.fruitBush; +const oMainHuntableAnimal = g_Gaia.mainHuntableAnimal; +const oSecondaryHuntableAnimal = g_Gaia.secondaryHuntableAnimal; +const oStoneLarge = g_Gaia.stoneLarge; +const oStoneSmall = g_Gaia.stoneSmall; +const oMetalLarge = g_Gaia.metalLarge; +const oMetalSmall = g_Gaia.metalSmall; + +const aGrass = g_Decoratives.grass; +const aGrassShort = g_Decoratives.grassShort; +const aRockLarge = g_Decoratives.rockLarge; +const aRockMedium = g_Decoratives.rockMedium; +const aBushMedium = g_Decoratives.bushMedium; +const aBushSmall = g_Decoratives.bushSmall; + +const pForest1 = [tForestFloor2 + TERRAIN_SEPARATOR + oTree1, tForestFloor2 + TERRAIN_SEPARATOR + oTree2, tForestFloor2]; +const pForest2 = [tForestFloor1 + TERRAIN_SEPARATOR + oTree4, tForestFloor1 + TERRAIN_SEPARATOR + oTree5, tForestFloor1]; + +const heightLand = 3; + +var g_Map = new RandomMap(heightLand, tMainTerrain); + +const numPlayers = getNumPlayers(); + +var clPlayer = g_Map.createTileClass(); +var clHill = g_Map.createTileClass(); +var clForest = g_Map.createTileClass(); +var clDirt = g_Map.createTileClass(); +var clRock = g_Map.createTileClass(); +var clMetal = g_Map.createTileClass(); +var clFood = g_Map.createTileClass(); +var clBaseResource = g_Map.createTileClass(); + +const mapCenter = g_Map.getCenter(); +var playerIDs = sortAllPlayers(); + +placePlayerBases({ + "PlayerPlacement": [sortAllPlayers(), ...playerPlacementMultiArcs( + playerIDs, + mapCenter, + fractionToTiles(0.35), + 0, + 0.75)], + "PlayerTileClass": clPlayer, + "BaseResourceClass": clBaseResource, + "CityPatch": { + "outerTerrain": tRoadWild, + "innerTerrain": tRoad + }, + "Chicken": { + }, + "Berries": { + "template": oFruitBush + }, + "Mines": { + "types": [ + { "template": oMetalLarge }, + { "template": oStoneLarge } + ] + }, + "Trees": { + "template": oTree1, + "count": 5 + }, + "Decoratives": { + "template": aGrassShort + } +}); +Engine.SetProgress(20); + +createBumps(avoidClasses(clPlayer, 20)); + +if (randBool()) + createHills([tCliff, tCliff, tHill], avoidClasses(clPlayer, 20, clHill, 15), clHill, scaleByMapSize(3, 15)); +else + createMountains(tCliff, avoidClasses(clPlayer, 20, clHill, 15), clHill, scaleByMapSize(3, 15)); + +var [forestTrees, stragglerTrees] = getTreeCounts(...rBiomeTreeCount(1)); +createDefaultForests( + [tMainTerrain, tForestFloor1, tForestFloor2, pForest1, pForest2], + avoidClasses(clPlayer, 20, clForest, 18, clHill, 0), + clForest, + forestTrees); + +Engine.SetProgress(50); + +g_Map.log("Creating dirt patches"); +createLayeredPatches( + [scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)], + [[tMainTerrain,tTier1Terrain],[tTier1Terrain,tTier2Terrain], [tTier2Terrain,tTier3Terrain]], + [1, 1], + avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12), + scaleByMapSize(15, 45), + clDirt); + +g_Map.log("Creating grass patches"); +createPatches( + [scaleByMapSize(2, 4), scaleByMapSize(3, 7), scaleByMapSize(5, 15)], + tTier4Terrain, + avoidClasses(clForest, 0, clHill, 0, clDirt, 5, clPlayer, 12), + scaleByMapSize(15, 45), + clDirt); +Engine.SetProgress(55); + +g_Map.log("Creating metal mines"); +createBalancedMetalMines( + oMetalSmall, + oMetalLarge, + clMetal, + avoidClasses(clForest, 1, clPlayer, scaleByMapSize(20, 35), clHill, 1) +); + +g_Map.log("Creating stone mines"); +createBalancedStoneMines( + oStoneSmall, + oStoneLarge, + clRock, + avoidClasses(clForest, 1, clPlayer, scaleByMapSize(20, 35), clHill, 1, clMetal, 10) +); + +Engine.SetProgress(65); + +var planetm = 1; + +if (currentBiome() == "generic/india") + planetm = 8; + +createDecoration( + [ + [new SimpleObject(aRockMedium, 1, 3, 0, 1)], + [new SimpleObject(aRockLarge, 1, 2, 0, 1), new SimpleObject(aRockMedium, 1, 3, 0, 2)], + [new SimpleObject(aGrassShort, 1, 2, 0, 1)], + [new SimpleObject(aGrass, 2, 4, 0, 1.8), new SimpleObject(aGrassShort, 3,6, 1.2, 2.5)], + [new SimpleObject(aBushMedium, 1, 2, 0, 2), new SimpleObject(aBushSmall, 2, 4, 0, 2)] + ], + [ + scaleByMapAreaAbsolute(16), + scaleByMapAreaAbsolute(8), + planetm * scaleByMapAreaAbsolute(13), + planetm * scaleByMapAreaAbsolute(13), + planetm * scaleByMapAreaAbsolute(13) + ], + avoidClasses(clForest, 0, clPlayer, 0, clHill, 0)); + +Engine.SetProgress(70); + +createFood( + [ + [new SimpleObject(oMainHuntableAnimal, 5, 7, 0, 4)], + [new SimpleObject(oSecondaryHuntableAnimal, 2, 3, 0, 2)] + ], + [ + 3 * numPlayers, + 3 * numPlayers + ], + avoidClasses(clForest, 0, clPlayer, 20, clHill, 1, clMetal, 4, clRock, 4, clFood, 20), + clFood); + +Engine.SetProgress(75); + +createFood( + [ + [new SimpleObject(oFruitBush, 5, 7, 0, 4)] + ], + [ + 3 * numPlayers + ], + avoidClasses(clForest, 0, clPlayer, 20, clHill, 1, clMetal, 4, clRock, 4, clFood, 10), + clFood); + +Engine.SetProgress(85); + +createStragglerTrees( + [oTree1, oTree2, oTree4, oTree3], + avoidClasses(clForest, 8, clHill, 1, clPlayer, 12, clMetal, 6, clRock, 6, clFood, 1), + clForest, + stragglerTrees); + +placePlayersNomad(clPlayer, avoidClasses(clForest, 1, clMetal, 4, clRock, 4, clHill, 4, clFood, 2)); + +g_Map.ExportMap(); Index: binaries/data/mods/public/maps/random/mainland_tg.json =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/random/mainland_tg.json @@ -0,0 +1,11 @@ +{ + "settings" : { + "Name" : "Mainland TG", + "Script" : "mainland_tg.js", + "Description" : "A typical map without any water; teammates are placed more closely together.", + "Preview" : "mainland.png", + "Keywords": ["multiplayer"], + "SupportedBiomes": "generic/", + "CircularMap" : true + } +} Index: binaries/data/mods/public/maps/random/rmgen-common/player.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen-common/player.js +++ binaries/data/mods/public/maps/random/rmgen-common/player.js @@ -843,3 +843,77 @@ return [playerIDs, startLocations]; } + +/** + * Place on an arc on a circle, 1 arc per team. Each team member is on the same arc. + * @param {Array[int]} playerIDs + * @param {float} center + * @param {float} radius + * @param {float} mapAngle + * @param {float} teamGapRatio Ratio difference between team gap and players on the same team Should be 0 to 1. + * e.g. 0.8 means the ratio team gap:team player gap is 8:2. n.b. < 0.5 means enemies are closer + * than team members are to each other + */ +function playerPlacementMultiArcs(playerIDs, center, radius, mapAngle, teamGapFrac) { + let playerTeams = playerIDs.map(getPlayerTeam); + let uniqueTeams = new Set(playerTeams); + let nTeams = uniqueTeams.size; + let nPlayers = playerIDs.length; + + let teamIntMap = {}; + let teamFreqPlayers = {}; + let teamPlayersIntMap = {}; + + // Shuffle team order. + let teamShuffle = function(length) { + let i = 0; + let array = Array.from(Array(length), () => i++); + for(let i = array.length - 1; i > 0; i--){ + const j = Math.round(Math.random() * (array.length-1)) + const temp = array[i] + array[i] = array[j] + array[j] = temp + } + return array; + }(nTeams); + + // Team to array (random) index map. + Array.from(uniqueTeams).map((val, i) => {teamIntMap[val] = teamShuffle[i];}); + // Player frequency in teams. + playerTeams.map((v) => teamFreqPlayers[v] ? teamFreqPlayers[v] += 1 : teamFreqPlayers[v] = 1); + // Team player int map. This is useful when positioning players on a team. + Array.from(uniqueTeams).map((val) => {teamPlayersIntMap[val] = 0;}); + + // I don't know at this point. Trust my brain. It's smarter than my brain. + // Something-something add the previous team player combos. + // It's some kind of "cumulative frequency" of player teams idk. + for (let key in teamPlayersIntMap) { + if (teamPlayersIntMap.hasOwnProperty(key)) { + for (let key2 in teamPlayersIntMap) { + if (teamPlayersIntMap.hasOwnProperty(key)) { + if (teamIntMap[key2] > teamIntMap[key]) { + teamPlayersIntMap[key2] += teamFreqPlayers[key]-1; + } + } + } + } + } + + const teamPlayerGapFrac = 1 - teamGapFrac; + + const totalGapCount = teamGapFrac*nTeams + teamPlayerGapFrac*(nPlayers-nTeams); + + const teamGapAngle = 2*Math.PI*teamGapFrac/totalGapCount; + const teamPlayerGapAngle = 2*Math.PI*teamPlayerGapFrac/totalGapCount; + + + function playerAngle(i) { + + return mapAngle + teamGapAngle*teamIntMap[playerTeams[i]] + teamPlayerGapAngle*((teamPlayersIntMap[playerTeams[i]]++)); + } + + return playerPlacementCustomAngle( + radius, + center, + playerAngle); +}