//Place a level block in a level. The preferred walls can be specified. These can only be guaranteed // if the level has the wall type (for example, preferLeftWall can be ignored if HasLeftWall is false). //If a wall isn't preferred at a position, an exit is used instead if Has*Exit is true. public void Place(World world, Dictionary<char, ISpecialTileDefinition> specialTiles, int x, int y, bool preferLeftWall = true, bool preferRightWall = true, bool preferTopWall = true, bool preferBottomWall = true, bool isBottomOfRoom = false, int numberOfEnemies = 0, List<Type> possibleEnemyTypes = null) { possibleEnemyTypes = possibleEnemyTypes ?? new List<Type>(); int BlockID = random.Next(int.MaxValue); bool[,] isActuallyEmpty = new bool[Width, Height]; bool[,] isActuallyWall = new bool[Width, Height]; FairRandomCollection<Vector2> possibleEnemyPositions = new FairRandomCollection<Vector2>(); FairRandomCollection<Vector2> possibleTurretPositions = new FairRandomCollection<Vector2>(); //Place the tiles in the level for (int i = 0; i < Width; i++) { for (int j = 0; j < Height; j++) { char data = Data[i, j]; //Check special tiles until we find a normal tile. int loopCount = 0; //Some tiles can be turned into walls if they end up on the sides of the level. bool specialTileTurnsIntoWallOnSides = false; while (specialTiles.ContainsKey(data)) { specialTileTurnsIntoWallOnSides = specialTileTurnsIntoWallOnSides || specialTiles[data].CanBeWall; List<string> specialKeywords = new List<string>(); //Contains the special keywords that are true at this moment. if (preferLeftWall) specialKeywords.Add("LEFTWALL"); if (preferRightWall) specialKeywords.Add("RIGHTWALL"); if (preferTopWall) specialKeywords.Add("TOPWALL"); if (preferBottomWall) specialKeywords.Add("BOTTOMWALL"); data = specialTiles[data].GetTile(BlockID, specialKeywords); if (loopCount > 1000) throw new Exception("Possible infinite loop detected! Please change the level definition file."); } //If the special tile can be turned into a wall, check if this should happen. if (specialTileTurnsIntoWallOnSides) { if (i == 0) //Left wall { if (preferLeftWall) data = '1'; } if (i == Width - 1) //Right wall { if (preferRightWall) data = '1'; } if (j == 0) //Top wall { if (preferTopWall) data = '1'; } if (j == Height - 1) //Bottom wall { if (preferBottomWall) data = '1'; } } int baseX = x + World.TileWidth * i, baseY = y + World.TileHeight * j; float centerX = baseX + World.TileWidth / 2, centerY = baseY + World.TileHeight / 2; Rectangle stdCollisionRect = new Rectangle(baseX, baseY, World.TileWidth, World.TileHeight); //If it's nothing or something the player can move through, and the bottom of the room, then add a jumpthrough. if (data == '.' | data == '/' | data == '\\' && j == Height - 1 && isBottomOfRoom) world.AddObject(new JumpThrough(new Rectangle(baseX, baseY, World.TileWidth, 1))); if (data == '1') //A wall { world.AddObject(new Wall(stdCollisionRect)); isActuallyWall[i, j] = true; if (j != 0 && isActuallyEmpty[i, j - 1]) { //We can place an enemy here! possibleEnemyPositions.Add(new Vector2(centerX, centerY - World.TileHeight + 10)); } } else if (data == 'T') //A tile world.AddObject(new WindowTile(stdCollisionRect)); else if (data == 'D') //A gun door world.AddObject(new GunDoor(), baseX + World.TileWidth / 2, baseY); else if (data == 'M') //A melee door world.AddObject(new MeleeDoor(), baseX + World.TileWidth / 2, baseY); else if (data == 'C') //A rocket door world.AddObject(new RocketDoor(), baseX + World.TileWidth / 2, baseY); else if (data == 'B') //A boss door world.AddObject(new BossDoor(), baseX + World.TileWidth / 2, baseY); else if (data == 'O') world.AddObject(new Portal(), baseX + 6, baseY); else if (data == 'S') //Spikes world.AddObject(new Spikes(), baseX, baseY + (int)(World.TileHeight * (100f / 128f))); else if (data == 'G') //A gun pickup block world.AddObject(new GunPickup(), centerX, centerY); else if (data == 'U') //A gun upgrade pickup block world.AddObject(new GunUpgrade(), centerX, centerY); else if (data == 'W') //A wrench pickup block world.AddObject(new WrenchPickup(), centerX, centerY); else if (data == 'R') //A rocket pickup block world.AddObject(new RocketPickup(), centerX, centerY); else if (data == 'H') //A health pickup block world.AddObject(new HealthDrop(10), centerX, centerY); else if (data == 'A') //First health upgrade world.AddObject(new MaxHPUpgrade(), centerX, centerY); else if (data == 'E') //Huge health upgrade world.AddObject(new HugeMaxHPUpgrade(), centerX, centerY); else if (data == 'F') //Drone upgrade world.AddObject(new DroneRangeUpgrade(), centerX, centerY); else if (data == 'P') //The player { world.Player = new Player(); world.AddObject(world.Player, centerX, centerY); } else if (data == 'J') //A jumpthrough block world.AddObject(new JumpThrough(new Rectangle(baseX, baseY, World.TileWidth, 1))); else if (data == '\\') //A slope world.AddObject(new SlopeLeft(stdCollisionRect)); else if (data == '/') //A slope world.AddObject(new SlopeRight(stdCollisionRect)); else if (data == '.') { //Nothing isActuallyEmpty[i, j] = true; if (j != 0 && isActuallyWall[i, j - 1]) { //We can place a turret here! possibleTurretPositions.Add(new Vector2(centerX, baseY + 15)); } } } } //Place some enemies. for (int i = 0; i < numberOfEnemies; i++) { if (possibleEnemyTypes.Contains(typeof(Turret)) && World.Random.Next(3) == 0) { if (possibleTurretPositions.Count != 0) world.AddObject(new Turret(), possibleTurretPositions.Get()); } else if (possibleEnemyPositions.Count != 0) { world.AddObject(Activator.CreateInstance(possibleEnemyTypes.Except(new List<Type>() { typeof(Turret) }).ToList().GetRandomItem()) as GameObject, possibleEnemyPositions.Get()); } } }
//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); } } }