//Generate the level public void Generate(World world, Vector2 position, List<RoomExit> roomExits = null, List<string> guaranteedSpecialBlocks = null, string theme = "", int enemyNum = 0, List<Type> possibleEnemyTypes = null) { //Convert null arguments into default values roomExits = roomExits ?? new List<RoomExit>(); guaranteedSpecialBlocks = guaranteedSpecialBlocks ?? new List<string>(); possibleEnemyTypes = possibleEnemyTypes ?? new List<Type>(); //Create vars World = world; //Create the basic grid for the level. This will contain the main path through the level. int hBlocks = Width / BlockWidth, vBlocks = Height / BlockHeight; LevelBlockRequirements[,] basicGrid = new LevelBlockRequirements[hBlocks, vBlocks]; int[,] enemyNumber = new int[hBlocks, vBlocks]; //List for positions with enemies FairRandomCollection<Point> pointsThatCanHaveEnemies = new FairRandomCollection<Point>(); //By default, all sides are walls, unless this is the bossroom, in which case all sides (except the walls) are open. for (int i = 0; i < hBlocks; i++) { for (int j = 0; j < vBlocks; j++) { pointsThatCanHaveEnemies.Add(new Point(i, j)); if (theme == "Boss") { basicGrid[i, j] = new LevelBlockRequirements(SideType.Exit); if (i == 0) basicGrid[i, j].LeftSideType = SideType.Wall; if (j == 0) basicGrid[i, j].TopSideType = SideType.Wall; if (i == hBlocks - 1) basicGrid[i, j].RightSideType = SideType.Wall; if (j == vBlocks - 1) basicGrid[i, j].BottomSideType = SideType.Wall; } else basicGrid[i, j] = new LevelBlockRequirements(SideType.Wall); } } //Create exits where they should be foreach (RoomExit exit in roomExits) { if (exit.Direction == Direction.Left) basicGrid[exit.Position.X, exit.Position.Y].LeftSideType = SideType.Exit; if (exit.Direction == Direction.Right) basicGrid[exit.Position.X, exit.Position.Y].RightSideType = SideType.Exit; if (exit.Direction == Direction.Up) basicGrid[exit.Position.X, exit.Position.Y].TopSideType = SideType.Exit; if (exit.Direction == Direction.Down) basicGrid[exit.Position.X, exit.Position.Y].BottomSideType = SideType.Exit; //Level blocks with an exit can't have enemies pointsThatCanHaveEnemies.Remove(new Point(exit.Position.X, exit.Position.Y)); } //Connect the exits ConnectPoints(basicGrid, roomExits.Select(exitPoint => exitPoint.Position).ToList()); //Connect each now unconnected point to a connected point ConnectUnconnectedPoints(basicGrid); //Prune unneeded walls PruneWalls(basicGrid); //Create the actual level grid LevelBlock[,] levelGrid = new LevelBlock[hBlocks, vBlocks]; //Get any border blocks from the guaranteed blocks. List<string> borderBlocks = guaranteedSpecialBlocks.Where(specialBlock => specialBlock.Contains("Border")).ToList(); if (borderBlocks.Count != 0) { foreach (string borderBlock in borderBlocks) { guaranteedSpecialBlocks.Remove(borderBlock); //Check which direction should be used Direction dir = Direction.Right; if (borderBlock.Contains("Left")) dir = Direction.Left; if (borderBlock.Contains("Right")) dir = Direction.Right; if (borderBlock.Contains("Up")) dir = Direction.Up; if (borderBlock.Contains("Down")) dir = Direction.Down; //Find the exit. RoomExit exit = roomExits.Find(ex => ex.Direction == dir); //And make sure the block is placed there. basicGrid[exit.Position.X, exit.Position.Y].Group = borderBlock; } } //And get the boss portal and place it on the bottom if (guaranteedSpecialBlocks.Contains("BossPortal")) { guaranteedSpecialBlocks.Remove("BossPortal"); basicGrid[World.Random.Next(1, hBlocks - 1), vBlocks - 1].Group = "BossPortal"; } //Handle any other guaranteed blocks foreach (string guaranteedBlock in guaranteedSpecialBlocks) { int i = 0, j = 0; do { i = World.Random.Next(hBlocks); j = World.Random.Next(vBlocks); } while (basicGrid[i, j].Group != ""); basicGrid[i, j].Group = guaranteedBlock; } //Choose the level blocks for (int i = 0; i < hBlocks; i++) { for (int j = 0; j < vBlocks; j++) { if (levelGrid[i, j] == null) { //Add the level theme basicGrid[i, j].Theme += theme; //Get a block that would fit at this position. LevelBlock foundBlock = GetPossibleLevelBlock(basicGrid[i, j]); if (foundBlock != null) levelGrid[i, j] = foundBlock; else throw new Exception("There's no block that would fit here! Please create more blocks (of group '" + basicGrid[i, j].Group + "') and add them to LevelBlocks.txt."); } } } //Add enemies for (int i = 0; i < enemyNum; i++) { Point placeEnemyAt = pointsThatCanHaveEnemies.Get(); enemyNumber[placeEnemyAt.X, placeEnemyAt.Y]++; } //And place them for (int i = 0; i < hBlocks; i++) { for (int j = 0; j < vBlocks; j++) { levelGrid[i, j].Place(World, specialTiles, (int) position.X + i * BlockWidth * World.TileWidth, (int) position.Y + j * BlockHeight * World.TileHeight, basicGrid[i, j].LeftSideType == SideType.Wall, basicGrid[i, j].RightSideType == SideType.Wall, basicGrid[i, j].TopSideType == SideType.Wall, basicGrid[i, j].BottomSideType == SideType.Wall, j == vBlocks - 1, enemyNumber[i, j], possibleEnemyTypes); } } }
//Reads the possible level blocks from the level file void GetLevelBlocks() { string levelBlocksData = File.ReadAllText("Content/LevelBlocks.txt"); //Handle the different types of enter (\r\n, \r, and \n) levelBlocksData = levelBlocksData.Replace("\r\n", "\n"); levelBlocksData = levelBlocksData.Replace("\r", "\n"); string[] levelBlocksLines = levelBlocksData.Split('\n'); int thisBlockDataHeight = 0; //The current height of a level block. LevelBlock currentLevelBlock = null; int totalIgnoreCount = 0; string blockSpecialGroup = ""; //Groups are used for special, named, blocks, like the starting position of the player. string blockTheme = ""; for (int i = 0; i < levelBlocksLines.Length; i++) { string line = levelBlocksLines[i]; //IGNORE and ATTENTION can be used if some lines shouldn't be processed. if (line.StartsWith("IGNORE")) totalIgnoreCount++; if (line.StartsWith("ATTENTION") && totalIgnoreCount > 0) totalIgnoreCount--; if (line.StartsWith("THEMEEND")) blockTheme = ""; if (totalIgnoreCount > 0) continue; if (line.StartsWith("SPECIAL")) { line = line.Replace("SPECIAL", ""); //Delete the SPECIAL part of the line. //If there's a space at the beginning now, remove that, too. if (line.StartsWith(" ")) line = line.Remove(0, 1); //Now, the rest of line is the special group for the next block. blockSpecialGroup = line; } else if (line.StartsWith("THEMESTART")) { line = line.Replace("THEMESTART", ""); //Delete the SPECIAL part of the line. //If there's a space at the beginning now, remove that, too. if (line.StartsWith(" ")) line = line.Remove(0, 1); //Now, the rest of line is the current theme of blocks. blockTheme = line; } else if (line.StartsWith("BLOCK")) { currentLevelBlock = new LevelBlock(BlockWidth, BlockHeight); thisBlockDataHeight = 0; levelBlocks.Add(currentLevelBlock); line = line.Replace("BLOCK", ""); //Delete the BLOCK part of the line. //If there's a space at the beginning now, remove that, too. if (line.StartsWith(" ")) line = line.Remove(0, 1); //Now check the arguments of the block. string[] arguments = line.Split(' '); //The first argument should contain information about the exits of the block. string exits = arguments[0]; if (exits.Contains("U")) currentLevelBlock.HasTopExit = true; if (exits.Contains("D")) currentLevelBlock.HasBottomExit = true; if (exits.Contains("L")) currentLevelBlock.HasLeftExit = true; if (exits.Contains("R")) currentLevelBlock.HasRightExit = true; if (exits.Contains("!")) currentLevelBlock.ExitsMustMatch = true; if (blockSpecialGroup != "") //If this block is part of a special block group, set that group. { currentLevelBlock.Group = blockSpecialGroup; blockSpecialGroup = ""; //The next block won't be part of a group by default. } currentLevelBlock.Theme = blockTheme; //The second argument (optional) should contain information about how often a block appears. if (arguments.Length >= 2) { string appearancePreferenceString = arguments[1]; try { currentLevelBlock.AppearancePreference = double.Parse(appearancePreferenceString, CultureInfo.InvariantCulture); } catch { currentLevelBlock.AppearancePreference = 1; } } } else if (currentLevelBlock != null) { if (!string.IsNullOrWhiteSpace(line)) //Set the level data. { for (int j = 0; j < line.Length; j++) { currentLevelBlock.Data[j, thisBlockDataHeight] = line[j]; } thisBlockDataHeight++; } else //An empty line means the end of the block definition of this block. { currentLevelBlock.UpdateLevelInfo(GetWallTiles()); currentLevelBlock = null; } } else if (line.StartsWith("DEFINE ")) //This is a special tile definition { ParseSpecialTileDefinition(line.Remove(0, "DEFINE ".Length)); } } }