/// <summary> /// Generates a rectabgular room (or hall) and tries to attach it to the specified door (FloorTile) /// </summary> /// <param name="rooms"></param> /// <param name="roomType"></param> /// <param name="preferredDoor"></param> /// <returns></returns> private static Room GenerateGenericRoom( List<Room> rooms, CastleRoomType roomType, FloorTile targetDoor ) { Room room = new Room(roomType); int tries = 0; int maxTries = 5; while( tries++ < maxTries ) { int w = 0; int h = 0; int numDoors = 0; int doorSpacing = 6; // used for LongHall only int hallWidth = 5; // used for LongHall only. Includes walls on either side : 3 should be the minimum, works best with odd numbers #region Specify sizes and number of doors, based on roomType switch( roomType ) { case CastleRoomType.Bedroom: w = rand.Next(5, 9) + 2; h = rand.Next(5, 9) + 2; numDoors = 1; break; case CastleRoomType.BedroomMaster: w = rand.Next(6, 10) + 2; h = rand.Next(6, 10) + 2; numDoors = 2; break; case CastleRoomType.Closet: w = rand.Next(1, 3) + 2; h = rand.Next(1, 3) + 2; numDoors = 1; break; case CastleRoomType.GreatRoomRectangle: w = rand.Next(10, 25) + 2; h = rand.Next(15, 25) + 2; numDoors = 6; break; case CastleRoomType.GreatRoomSquare: w = rand.Next(10, 25) + 2; h = w; numDoors = 6; break; case CastleRoomType.GreatroomCircle: w = rand.Next(15,30) + 2; if( w%2==0 ) w++; h = w; numDoors = 2*(rand.Next(6)+1); break; case CastleRoomType.Kitchen: w = rand.Next(3, 11) + 2; h = rand.Next(3, 11) + 2; numDoors = 3; break; case CastleRoomType.Storage: w = rand.Next(2, 5) + 2; h = rand.Next(2, 5) + 2; numDoors = 1; break; case CastleRoomType.LongHall: if( rand.Next(2) == 0 ) { w = doorSpacing * (1+rand.Next(8)); h = hallWidth; numDoors = w/doorSpacing; } else { w = hallWidth; h = doorSpacing * (1+rand.Next(8)); numDoors = h/doorSpacing; } break; } #endregion room.cells = new FloorTile[w, h]; room.doors.Clear(); switch( roomType ) { case CastleRoomType.GreatroomCircle: #region Circular Greatroom may have pillars (and generation is more complex than rectangular rooms) // fill room dimensions with wall for( int i = 0; i < w; i++ ) for( int j = 0; j < h; j++ ) room.cells[i, j] = new FloorTile(i, j, FloorType.Wall); // hollow out center, leaving a border of wall tiles for( int i = 1; i<w-1; i++ ) for( int j = 1; j<h-1; j++ ) if( (int)Math.Sqrt(Math.Pow(w/2 - i, 2.0) + Math.Pow(h/2 - j, 2.0)) < w/2.0-1 ) room.cells[i, j].type = FloorType.Floor; // Add pillars around the middle? if( rand.Next(2)==0 ) { int pillars = (1+rand.Next(6))*2; double dist = rand.Next(3, w/2 - 2); for( int i=0; i<pillars; i++ ) { room.cells[(int)Math.Round(w/2 - dist * Math.Cos(i*2.0*Math.PI/pillars), 0), (int)Math.Round(w/2 - dist * Math.Sin(i*2.0*Math.PI/pillars), 0) ].type = FloorType.Wall; } } #endregion break; case CastleRoomType.GreatRoomRectangle: case CastleRoomType.GreatRoomSquare: #region Greatrooms may have pillars // make solid wall outline, hollow center for( int i = 0; i < w; i++ ) for( int j = 0; j < h; j++ ) { room.cells[i, j] = new FloorTile(i, j); if( i == 0 || i == w - 1 || j == 0 || j == h - 1 ) room.cells[i, j].type = FloorType.Wall; else room.cells[i, j].type = FloorType.Floor; } bool NSpillars = false; bool EWpillars = false; // Add pillars around the middle? switch( rand.Next(4) ) { case 1: // all the way around the room NSpillars = EWpillars = true; break; case 2: // N/S walls only NSpillars = true; break; case 3: // E/W walls only EWpillars = true; break; default: // no pillars. break; } // spacing for pillars should be consistent within a room int offset = rand.Next(2,6); int spacing = rand.Next(2,8); if( NSpillars ) for( int i=offset; i<=w/2; i+=spacing ) { room.cells[i, offset].type = FloorType.Wall; room.cells[i, h-1-offset].type = FloorType.Wall; room.cells[w-1-i, offset].type = FloorType.Wall; room.cells[w-1-i, h-1-offset].type = FloorType.Wall; } if( EWpillars ) for( int i=offset; i<=h/2; i+=spacing ) { room.cells[offset, i].type = FloorType.Wall; room.cells[w-1-offset, i].type = FloorType.Wall; room.cells[offset, h-1-i].type = FloorType.Wall; room.cells[w-1-offset, h-1-i].type = FloorType.Wall; } #endregion break; default: #region All other rectangualr rooms, including LongHall // make solid wall outline, hollow center for( int i = 0; i < w; i++ ) for( int j = 0; j < h; j++ ) { room.cells[i, j] = new FloorTile(i, j); if( i == 0 || i == w - 1 || j == 0 || j == h - 1 ) room.cells[i, j].type = FloorType.Wall; else room.cells[i, j].type = FloorType.Floor; } #endregion break; } // So far, only applicable to LongHall and GreatRooms // If we haven't connected to a GreatRoom after half the max tried, stop limiting the attemps. // This setting has no effect on other room connections (so far). bool allowConnectionBias = tries < maxTries/2; if( !PlaceRoom( rooms, room, roomType, allowConnectionBias, targetDoor ) ) continue; // placed a room via connection - remove that door from the number we're going to generate. // (closets, for example, should have that connection count as their only door) if( rooms.Count > 0 ) numDoors--; // set doors if( roomType == CastleRoomType.GreatroomCircle ) { #region Circular rooms will only place doors along the "flat" sections near N, E, S, W sides. int count = 0; int maxDoorTries = 30; int doorTries = 0; while( count < numDoors && doorTries < maxDoorTries ) { double angle = rand.Next(360) * Math.PI / 180.0; int tX = (int)Math.Round((w/2) + (w-1)/2 * Math.Cos(angle), 0); int tY = (int)Math.Round((w/2) + (w-1)/2 * Math.Sin(angle), 0); bool EW = tX>0 && tX<w-1 && (room.cells[tX-1, tY].type == FloorType.Wall) // || room.cells[tX-1, tY].type == FloorType.DoorClosed) && (room.cells[tX+1, tY].type == FloorType.Wall); // || room.cells[tX+1, tY].type == FloorType.DoorClosed); bool NS = tY>0 && tY<h-1 && (room.cells[tX, tY-1].type == FloorType.Wall) // || room.cells[tX, tY-1].type == FloorType.DoorClosed) && (room.cells[tX, tY+1].type == FloorType.Wall); // || room.cells[tX, tY+1].type == FloorType.DoorClosed); if( EW ^ NS ) // exclusive OR : must be one or the other, but not neither, and not both. { room.doors.Add(room.cells[tX, tY]); room.cells[tX, tY].type = FloorType.DoorClosed; count++; } else { // failed to place - up the count doorTries++; } } #endregion } else if( roomType == CastleRoomType.LongHall ) { #region Special case : add doors one each end, and evenly spaced down both sides if( w > h ) { // E/W hall room.doors.Add(room.cells[0, h/2]); room.doors.Add(room.cells[w-1, h/2]); for( int i = 0; i < w/doorSpacing; i++ ) { room.doors.Add(room.cells[doorSpacing/2 + i*doorSpacing, 0+0]); room.doors.Add(room.cells[doorSpacing/2 + i*doorSpacing, h-1]); } } else { // N/S hall room.doors.Add(room.cells[w/2, 0]); room.doors.Add(room.cells[w/2, h-1]); for( int i = 0; i < h/doorSpacing; i++ ) { room.doors.Add(room.cells[0+0, doorSpacing/2 + i*doorSpacing]); room.doors.Add(room.cells[w-1, doorSpacing/2 + i*doorSpacing]); } } foreach( FloorTile door in room.doors ) door.type = FloorType.DoorClosed; #endregion } else { #region All other square rooms for( int i = 0; i < numDoors; i++ ) { if( rand.Next(2) == 0 ) // top/bottom... x= whatever, and y=1 (top edge) or y=h-2 (bottom edge) room.doors.Add(room.cells[rand.Next(1, w - 2), rand.Next(2) * (h - 1)]); else // left/right... x=1 (left side) or x=w-2 (right side) and y = whatever room.doors.Add(room.cells[rand.Next(2) * (w - 1), rand.Next(1, h - 2)]); room.doors[room.doors.Count-1].type = FloorType.DoorClosed; } #endregion } return room; } // could not find a valid connection for this room... gave up return null; }
/// <summary> /// True = valid placement /// False = invalid (overlap found) /// </summary> /// <param name="room"></param> /// <param name="door"></param> /// <returns></returns> private static bool CheckRoomTilesAgainstGrid( Room room, FloorTile door ) { FloorType gcType; FloorType rcType; foreach( FloorTile roomCell in room.cells ) { // bounds checking - id it would fall off the scope of the base grid[,], then it's a no-go. if( roomCell.x < 1 || roomCell.x >= grid.GetLength(0)-1 || roomCell.y<1 || roomCell.y>=grid.GetLength(1)-1 ) return false; gcType = grid[roomCell.x, roomCell.y].type; rcType = roomCell.type; if( (rcType != FloorType.Wall && gcType != FloorType.Wall) || (rcType == FloorType.Wall && gcType != FloorType.Wall && !(roomCell.x==door.x && roomCell.y==door.y)) ) { return false; } } // checked everything and it was all OK. Valid placement. return true; }
/// <summary> /// True = valid placement found, room has been updated to the correct offset. /// False = no valid connections, room has been updated to last tried offset (but is probably useless). /// </summary> /// <param name="room"></param> /// <param name="door"></param> /// <returns></returns> private static bool CheckRoomAgainstGrid( Room room, FloorTile door ) { return CheckRoomAgainstGrid(room, door, new Directions[] { Directions.North, Directions.East, Directions.South, Directions.West }); }
/// <summary> /// True = valid placement found, room has been updated to the correct offset. /// False = no valid connections, room has been updated to last tried offset (but is probably useless). /// </summary> /// <param name="room"></param> /// <param name="door"></param> /// <param name="directions"></param> /// <returns></returns> private static bool CheckRoomAgainstGrid( Room room, FloorTile door, Directions[] directions ) { // kind of cumbersome, but allows random selection and deletion by reference. // Arrays are more difficult to remove things from. List<Directions> dirs = new List<Directions>(); for( int i=0; i<directions.Length; i++ ) dirs.Add(directions[i]); // try attaching to the north int w = room.cells.GetLength(0); int h = room.cells.GetLength(1); Directions d; // Pick a direction specified, and test it. // Avoids obvious bias against North, and allows specific tests for certain room types (halls, for example) while( dirs.Count > 0 ) { d = dirs[rand.Next(dirs.Count)]; dirs.Remove(d); switch( d ) { case Directions.North: // try attaching to the north room.SetRoomPosition(door.x - w/2, door.y-h+1); if( CheckRoomTilesAgainstGrid(room, door) ) return true; break; case Directions.South: // try attaching to the south room.SetRoomPosition(door.x - w/2, door.y); if( CheckRoomTilesAgainstGrid(room, door) ) return true; break; case Directions.East: // try attaching to the east room.SetRoomPosition(door.x-w+1, door.y-h/2); if( CheckRoomTilesAgainstGrid(room, door) ) return true; break; case Directions.West: // try attaching to the west room.SetRoomPosition(door.x, door.y-h/2); if( CheckRoomTilesAgainstGrid(room, door) ) return true; break; } } // no valid connection found. return false; }
/// <summary> /// Tries to place the specified room. /// True = successful, and the connecting door has been flagged. /// False = unable to find a connection. /// </summary> /// <param name="rooms"></param> /// <param name="room"></param> /// <param name="roomType"></param> /// <param name="allowConnectionBias"></param> /// <param name="targetDoor"></param> /// <returns></returns> private static bool PlaceRoom(List<Room> rooms, Room room, CastleRoomType roomType, bool allowConnectionBias, FloorTile targetDoor ) { int w = room.cells.GetLength(0); int h = room.cells.GetLength(1); if( rooms.Count == 0 ) { // place in middle room.SetRoomPosition(grid.GetLength(0)/2 - w/2, grid.GetLength(1)/2 - h/2); } else { // pick a door to attach to, build from there. List<FloorTile> doors = new List<FloorTile>(); FloorTile connectionDoor = null; if( targetDoor != null ) { doors.Add(targetDoor); } else if( allowConnectionBias ) { switch( roomType ) { case CastleRoomType.LongHall: // custom bias to connect long Halls to Great Rooms if at all possible. foreach( Room r in rooms ) if( r.type == CastleRoomType.GreatroomCircle ||r.type == CastleRoomType.GreatRoomSquare ||r.type == CastleRoomType.GreatRoomRectangle ) foreach( FloorTile door in r.doors ) if( door.type == FloorType.DoorClosed) doors.Add(door); break; case CastleRoomType.GreatroomCircle: case CastleRoomType.GreatRoomRectangle: case CastleRoomType.GreatRoomSquare: // Greatrooms should try to attach to the end of hallways. // Since the LongHall are generated with the end doors first, we can // reference those tow doors directly without searching though the room's doors list foreach( Room r in rooms ) if( r.type == CastleRoomType.LongHall ) { if( r.doors[0].type == FloorType.DoorClosed) doors.Add(r.doors[0]); if( r.doors[1].type == FloorType.DoorClosed) doors.Add(r.doors[1]); } break; default: // no custom doors list - the default of "All of them" will be used below break; } } // Check for custom doors list - if there are none, default to use all doors in all rooms for tries. if( doors.Count == 0 ) { foreach( Room r in rooms ) foreach( FloorTile door in r.doors ) if( door.type == FloorType.DoorClosed) doors.Add(door); } // In the event of a LongHall, we'd like to attach it so that it // traveles away from the room being attached to, rather than attaching along the side of the hall. Directions[] directions; if( roomType == CastleRoomType.LongHall ) { if( w>h ) { // E/W hall - try to attach on East or West side only directions = new Directions[] { Directions.East, Directions.West }; } else { // N/S hall - attach north or south only. directions = new Directions[] { Directions.North, Directions.South }; } } else { // all other rooms may try to attach from any direction directions = new Directions[] { Directions.North, Directions.East, Directions.South, Directions.West }; } while( doors.Count>0 && connectionDoor==null ) { // pick a door // try to attach to it to the N/E/W/S // update room position accordingly // check room.cells against all other room.cells // break loop when successful FloorTile door = doors[rand.Next(doors.Count)]; doors.Remove(door); if( CheckRoomAgainstGrid(room, door, directions) ) { connectionDoor = door; break; } } // failed to place room if( connectionDoor == null ) return false; //set the connection door foreach( FloorTile cell in room.cells ) if( cell.x==connectionDoor.x && cell.y==connectionDoor.y ) { cell.type = FloorType.DoorOpen; connectionDoor.type = FloorType.DoorOpen; break; } } return true; }