/// <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 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> /// 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> /// 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; }
/// <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> /// For the mazes that are "drawn" rather than generated, this will convert /// the bitmap to a FloorTile grid, using the colors specified in GenerateMaze() /// </summary> /// <param name="pic"></param> /// <returns></returns> private static FloorTile[,] TransposePicToGrid( Bitmap pic ) { //tileColor = new Color[Enum.GetValues(typeof(FloorType)).Length]; //tileColor[(int)FloorType.DoorClosed] = Color.Brown; //tileColor[(int)FloorType.DoorOpen] = Color.Tan; //tileColor[(int)FloorType.Wall] = Color.DarkGray; //tileColor[(int)FloorType.DoorSecret] = Color.DarkGray; //tileColor[(int)FloorType.Floor] = Color.BlanchedAlmond; //tileColor[(int)FloorType.Trap] = Color.BlanchedAlmond; //tileColor[(int)FloorType.StairsDown] = Color.Green; //tileColor[(int)FloorType.StairsUp] = Color.DarkGreen; grid = new FloorTile[pic.Width, pic.Height]; for( int i=0; i<pic.Width; i++ ) { for( int j=0; j<pic.Height; j++ ) { grid[i, j] = new FloorTile(i, j); if( i==0 || i==pic.Width-1 || j==0 || j==pic.Height-1 ) grid[i, j].type = FloorType.Wall; else { Color pixelColor = pic.GetPixel(i, j); for( int c=0; c<TileColor.Length; c++ ) if( pixelColor.ToArgb() == TileColor[c].ToArgb() ) { grid[i, j].type = (FloorType)Enum.Parse(typeof(FloorType), c.ToString()); break; } } } } return grid; }
/// <summary> /// Implimentation fo A* pathing algorithm to find a path from one floortile to another. /// Used for generation of the Dungeon style mazes to display the room connections. /// Modified from mob AI pathfinding to disallow diagonal paths. /// /// parms (as selected in the calling method) is an array of weights to apply: /// parms[0] = continueStraight /// parms[1] = turningWeight /// parms[2] = pathNeighborWeight /// </summary> /// <param name="maze"></param> /// <param name="start"></param> /// <param name="end"></param> /// <param name="dlg"></param> /// <param name="parms"></param> /// <returns></returns> private static ArrayList FindPath( FloorTile[,] maze, FloorTile start, FloorTile end, DM_Uber_Tool.ProgressDialog dlg, object[] parms ) { // // pathing applies the following weights to cells : // // default weight to move at all. Applied to all options. int baseWeight = 10; // adjust to affect pathing - long straight paths, or semi-windy ones int continueStraight = (int)parms[0]; int turningWeight = (int)parms[1]; int pathNeighborWeight = (int)parms[2]; // Following implimentation for room connections : // N/S/E/W only (no diagonals, unlike mob pathing) and // must be at leaast 1 cell away from the edge of the map (allow for border) // clear prev pathing for( int i=0; i<maze.GetLength(0); i++ ) for( int j=0; j<maze.GetLength(1); j++ ) maze[i, j].cameFrom = null; // Initially, all rooms are in open (unconnected) set. // Once connected,the room is moved to the closed (connected) set. ArrayList closedSet = new ArrayList(); ArrayList openSet = new ArrayList(); maze[start.x, start.y].G = 0; // G-Score = distance from self to this cell. Starting cell distance = 0; maze[start.x, start.y].H = 10*(Math.Abs(start.x-end.x) + Math.Abs(start.y-end.y)); // Guess at distance from start cell to hero - calculated with no diagonals, and a tile weight of 10 (arbitrary) maze[start.x, start.y].F = 0; // F-Score = total distance traveled from start to this cell. Start cell traveled dist = 0 openSet.Add(maze[start.x, start.y]); // Open set is to be evaluated. Add starting cell to this set. // evaluate cells in openSet until the hero cell is found while( openSet.Count>0 ) { openSet.Sort(); // To always pick the next cell with the lowest actual traveled distance. FloorTile current = openSet[0] as FloorTile; // grab the next path square with the lowest F-Score value // prgress dialog pointer set dlg.current = current; if( current == maze[end.x, end.y] ) { // Path found : assemble path, return list of cells ArrayList path = new ArrayList(); while( current.cameFrom != null ) { path.Add(new Point(current.x, current.y)); current = current.cameFrom; } path.Add(new Point(start.x, start.y)); return path; } openSet.Remove(current); // cell being evaluated is removed from the openSet closedSet.Add(current); // ... and added to the closedSet // add all valid neighbors-of-current cells to the openSet for( int i=-1; i<=1; i++ ) { for( int j=-1; j<=1; j++ ) { if( (i==-1||i==1)&&(j==-1||j==1) ) continue; // no diagonal paths to connect rooms if( current.x+i <= 0 ||current.x+i >= maze.GetLength(0)-1 ||current.y+j <= 0 ||current.y+j >= maze.GetLength(1)-1 ) continue; // no cells on boarder or map FloorTile neighbor = maze[current.x+i, current.y+j]; // if the neighbor has already been looked at, don't revisit it. if( closedSet.Contains(neighbor) ) continue; // determine tenative G-Score for neighbor cell int tenativeGScore = current.G; // base value = current cell G-Score // Add a weight to move to the neighbor cell tenativeGScore += baseWeight; if( current.cameFrom != null && current.cameFrom.cameFrom != null ) { if( (neighbor.x == current.x && current.x == current.cameFrom.x) ||(neighbor.y == current.y && current.y == current.cameFrom.y) ) tenativeGScore += continueStraight; else tenativeGScore += turningWeight; } // Add a penalty for each path neighbor cell this cell has // (highly discourage traveling through rooms, try to avoid crossing paths) for( int ni=-1; ni<=1; ni++ ) for( int nj=-1; nj<=1; nj++ ) if( !(maze[neighbor.x+ni, neighbor.y+nj].type==FloorType.Wall) ) tenativeGScore += pathNeighborWeight; bool tenativeGScoreIsBetter = false; // pessimisitc default if( !openSet.Contains(neighbor) ) { openSet.Add(neighbor); tenativeGScoreIsBetter = true; } else if( tenativeGScore < current.G ) { tenativeGScoreIsBetter = true; } // set values and cameFrom 'pointers' if( tenativeGScoreIsBetter ) { neighbor.cameFrom = current; neighbor.G = tenativeGScore; neighbor.H = 10*(Math.Abs(neighbor.x-end.x) + Math.Abs(neighbor.y-end.y)); // arbitrary heuristic - new distance to goal cell, straight line neighbor.F = neighbor.G + neighbor.H; } } } } // Clean up after myself openSet.Clear(); closedSet.Clear(); return new ArrayList(); // no path found }
/// <summary> /// Random room placement, then random rall connections /// /// Pplace several rooms int the maze /// Pathing algorithm to connect wall cells to another room's wall cell /// Unlike mob and player patihing, only horizontal and vertical moves are allowed. /// Pathing weights /// To create hallways, cells that neighbor already pathed cells will have a penalty to their weight. /// If pathing tunels into an existing tunnel, existing tunnel cells will have a weight of 1/2 normal. /// /// height*width*poathwidth will be used to create the total area to place rooms in /// rooms will be 1x to 2x pathWidth in size, i.e.: random( pathwidth, 2*pathWidth) /// </summary> /// <param name="width"></param> /// <param name="height"></param> /// <param name="pathWidth"></param> /// <returns></returns> private static FloorTile[,] GenerateStyle_Dungeon( int width, int height, int pathWidth ) { DM_Uber_Tool.ProgressDialog dlg = new DM_Uber_Tool.ProgressDialog(); dlg.status = "Initializing..."; dlg.ShowProgressDialog(); // set overall maze size Bitmap pic = new Bitmap(width*pathWidth, height*pathWidth); //FloorTile[,] grid = new FloorTile[width*pathWidth, height*pathWidth]; grid = new FloorTile[width * pathWidth, height * pathWidth]; for( int i=0; i<width*pathWidth; i++ ) for( int j=0; j<pathWidth*height; j++ ) grid[i, j] = new FloorTile(i, j); Graphics g = Graphics.FromImage(pic); g.Clear(TileColor[(int)FloorType.Wall]); // all walls up Pen wallsUp = new Pen(TileColor[(int)FloorType.Wall], 1); Pen wallsDown = new Pen(TileColor[(int)FloorType.Floor], 1); ArrayList rooms = new ArrayList(); // place rooms - must have a 'roomSpacing'-cell border to allow pathing along the edges int roomSpacing = 3; int rx=0, ry=0, rw=0, rh=0; int tries = 0; int totalRooms = 0; while( tries < 50 ) { rx = rand.Next(roomSpacing, width*pathWidth-1-roomSpacing); ry = rand.Next(roomSpacing, height*pathWidth-1-roomSpacing); rw = rand.Next(pathWidth, pathWidth*3); rh = rand.Next(pathWidth, pathWidth*3); bool valid = true; // check room boundaries - no overlaps, 3 cell gap between them all for( int i=rx-roomSpacing; i<rx+rw+roomSpacing && valid; i++ ) for( int j=ry-roomSpacing; j<ry+rh+roomSpacing && valid; j++ ) { valid = i >= 0 && i < width*pathWidth && j >= 0 && j < height*pathWidth && grid[i, j].type==FloorType.Wall; } if( valid ) { ArrayList room = new ArrayList(); // flags for checking new rooms for( int i=rx; i<rx+rw; i++ ) for( int j=ry; j<ry+rh; j++ ) { // flag set to part of the room grid[i, j].type = FloorType.Floor; // borders of room added to list of potential cells to connect to other rooms // elaborate conditions are to attempt to not use the corner cells of the room for connections //if( ((i==rx || i==rx+rw-1) && (j>ry && j<ry+rh-1)) // || ((j==ry || j==ry+rh-1) && (i>rx && i<rx+rw-1)) // ) // room.Add( m[i, j] ); // edge wall - add to room border if( i==rx && (j>ry && j<ry+rh-1) ) room.Add(grid[i-1, j]); if( i==rx+rw-1 && (j>ry && j<ry+rh-1) ) room.Add(grid[i+1, j]); if( j==ry && (i>rx && i<rx+rw-1) ) room.Add(grid[i, j-1]); if( j==ry+rh-1 && (i>rx && i<rx+rw-1) ) room.Add(grid[i, j+1]); } rooms.Add(room); // picture of maze g.FillRectangle(wallsDown.Brush, rx, ry, rw, rh); totalRooms++; tries = 0; } else tries++; } dlg.flags = grid; dlg.status = string.Format("Connected {0}/{1} rooms...", totalRooms-rooms.Count, totalRooms); dlg.percentDone = (double)(totalRooms-rooms.Count)/totalRooms; // find center-most room ArrayList centerRoom = (ArrayList)rooms[0]; ArrayList connected = new ArrayList(); int r1x=0, r1y=0, r2x=0, r2y=0, dist = int.MaxValue; foreach( ArrayList room in rooms ) { if( room == centerRoom ) continue; r1x = ((FloorTile)room[0]).x; r1y = ((FloorTile)room[0]).y; r2x = grid.GetLength(0)/2; r2y = grid.GetLength(1)/2; int tmpDist = (int)(Math.Pow(r1x-r2x, 2)+Math.Pow(r1y-r2y, 2)); if( tmpDist < dist ) { centerRoom = room; dist = tmpDist; } } connected.Add(centerRoom); rooms.Remove(centerRoom); // rooms ready to connect - define pathing weights/rules int turningWeight=0; int continueStraight = 0; int pathNeighborWeight = 0; switch( rand.Next(3) ) { case 0: // favor long, straight horiz and vert paths turningWeight += 10; break; case 1: // favor long diagonal paths (i.e. : left, up, left, up, left, up instead of left, left, left, left, up, up, up, up) continueStraight += 10; break; default: // no change - paths between rooms will be somewhat haphazard and jaggedy. break; } // 2-in-3 chance to bais _against_ tunneling through existing paths, but allows if needed. // Will try to tunnel around existing paths when set, otherwise the most direct route (through paths or rooms alike) will be used. pathNeighborWeight = rand.Next(3)==0 ? 5 : 20; /* * DEBUG - override random set values for specific pathing behavior. */ //continueStraight = 0; //turningWeight = 20; //pathNeighborWeight = 50; object[] parms = new object[] { continueStraight, turningWeight, pathNeighborWeight }; // for each room, pick 1 or more cells that will connect to the rest // add these cells to the list of doorsToConnect and Doors while( rooms.Count > 0 ) { // pick the closest room to center from rooms (unconnected), dist = int.MaxValue; ArrayList room1 = null; if( rand.Next(100) == 0 ) // 1% chance to grab ANY room to connect (leads to occasional long path to wherever, takes longer to path to) { room1 = (ArrayList)(rooms[rand.Next(rooms.Count)]); } else { foreach( ArrayList tmpRoom in rooms ) { r1x = ((FloorTile)tmpRoom[0]).x; r1y = ((FloorTile)tmpRoom[0]).y; r2x = grid.GetLength(0)/2; r2y = grid.GetLength(1)/2; int tmpDist = (int)(Math.Pow(r1x-r2x, 2)+Math.Pow(r1y-r2y, 2)); if( tmpDist < dist ) { room1 = tmpRoom; dist = tmpDist; } } } // find closest connected room to room1 - these will be connected dist = int.MaxValue; ArrayList room2 = null; if( rand.Next(100) == 0 ) // 1% chance to grab ANY room to connect (leads to occasional long path to wherever, takes longer to path to) { room2 = (ArrayList)(connected[rand.Next(connected.Count)]); } else { foreach( ArrayList tmpRoom in connected ) { r1x = ((FloorTile)tmpRoom[0]).x; r1y = ((FloorTile)tmpRoom[0]).y; r2x = ((FloorTile)room1[0]).x; r2y = ((FloorTile)room1[0]).y; int tmpDist = (int)(Math.Pow(r1x-r2x, 2)+Math.Pow(r1y-r2y, 2)); if( tmpDist < dist ) { room2 = tmpRoom; dist = tmpDist; } } } FloorTile start = (FloorTile)room1[rand.Next(room1.Count)]; FloorTile end = (FloorTile)room2[rand.Next(room2.Count)]; connected.Add(room1); rooms.Remove(room1); dlg.flags = grid; dlg.start = start; dlg.end = end; dlg.status = string.Format("Connected {0}/{1} rooms...", totalRooms-rooms.Count, totalRooms); dlg.percentDone = (double)(totalRooms-rooms.Count)/totalRooms; ArrayList path = FindPath(grid, start, end, dlg, parms); foreach( Point p in path ) { grid[p.X, p.Y].type = FloorType.Floor; pic.SetPixel(p.X, p.Y, TileColor[(int)FloorType.Floor]); } } dlg.CloseProgressDialog(); return TransposePicToGrid(pic); }
/// <summary> /// Proposed implimentation : assume a tile is about 2ft square /// Great Rooms/Halls (30-40ft by 60-100 ft, rectangular ) /// Living rooms (15-20 ft square) /// Bedrooms (small - 10ft to 15ft square or rectangular) /// Closet/Storage rooms (tiny - 4ft to 6ft square or rectangular) /// /// Connections : /// Place largest room first /// Spawn hallways from that room (based on room size - great halls should get several, living areas get 2, or soemthing similar to that logic) /// Chance to spawn halls of arbitrary length from existing halls (make a more detailed castle layout?) /// /// Place remaining rooms/closets along halls/rooms with 1 cell wall between and open a path to the nearest room or rooms /// Halls get as many as they have rooms neighboring, /// Living areas get several, but not necessarily one for every room /// Bedrooms get one /// Closets get one /// /// After all rooms get placed, scan hallways for open wall connections, and attach closets sporatically to fill them out some /// /// Possibilities : /// Percent total area covered by each room type /// Generate Great Halls until that percent area is covered, /// Generate Living Areas until that percent area is covered, /// Generate Bedrooms until that percent area is covered, /// Spawn closets and storage like crazy /// Stop generation loop once closet count is reached, all bedrooms have at least one closet OR no valid spots left to add. /// /// Specific count of each room type (possibly easier) : /// If Greatroom count > 0 /// Spawn greatroom(s) /// Spawn hallway(s) /// Connect Greatrooms if multiple /// /// If Living Areas count > 0 /// Spawn living areas /// Spawn hallways /// Connect to all rooms within a certain radius? Well connected floorplan...? /// Spawn secret passages (muahaha...) /// /// If Bedroom count > 0 /// Spawn bedrooms /// Spawn closets /// Spawn secret passages (even more muahaha...) /// /// Spawn closets along remaining hallways, minimum distance from existing rooms/doorways (avoid over populating with closets, or stop at set count?) /// Spawn secret passages from closets (rediculous quantities of muahaha...) /// /// For each room type, auto spawn specific loot - i.e.: pre-populated (pseudo random) chests. /// </summary> /// <param name="width"></param> /// <param name="height"></param> /// <param name="pathWidth"></param> /// <param name="percentFull"></param> /// <returns></returns> private static FloorTile[,] GenerateStyle_Castle( int width, int height, int pathWidth, float percentFull ) { // Progress dialog usually doesn't get to display for too long... might not be needed. DM_Uber_Tool.ProgressDialog dlg = new DM_Uber_Tool.ProgressDialog(); // set overall maze size and initialize each cell int castleScale = 1; grid = new FloorTile[width * pathWidth * castleScale, height * pathWidth * castleScale]; for( int i = 0; i < width * pathWidth * castleScale; i++ ) for( int j = 0; j < pathWidth * height * castleScale; j++ ) grid[i, j] = new FloorTile(i, j); // defaults to Wall // set progress dialog references dlg.ShowProgressDialog(); dlg.flags = grid; dlg.status = "Placing rooms..."; dlg.percentDone = 0; List<Room> rooms = new List<Room>(); Room room = null; List<CastleRoomType> roomsToPlace = CreateCastleLayout(); if( roomsToPlace == null ) { #region If the layout is null, this will simply jam room after room into the availabel area until it can't fit any more int tries = 0; int maxTries = 5; while( tries++ < maxTries ) { int doorCount = 0; foreach( Room r in rooms ) foreach( FloorTile d in r.doors ) if( d.type == FloorType.DoorClosed ) doorCount++; dlg.status = string.Format("Placing Rooms (fails={0}, rooms={1}, doors={2})", tries, rooms.Count, doorCount); CastleRoomType roomType = (CastleRoomType)rand.Next(Enum.GetValues(typeof(CastleRoomType)).Length); if( (room = GenerateGenericRoom(rooms, roomType)) != null ) { // to list of existing rooms for next room to refernce doors from rooms.Add(room); // update the grid[,] of placed tiles foreach( FloorTile cell in room.cells ) grid[cell.x, cell.y].type = cell.type; tries = 0; // reset once a room is placed } } #endregion } else { #region Specified layout provided - will try to use only those rooms, in the specified order when placing // For each room in the list of rooms to place, try to place that room for( int r = 0; r < roomsToPlace.Count; r++ ) { if( (room = GenerateGenericRoom(rooms, roomsToPlace[r])) != null ) { // to list of existing rooms for next room to refernce doors from rooms.Add(room); // update the grid[,] of placed tiles foreach( FloorTile cell in room.cells ) grid[cell.x, cell.y].type = cell.type; } } #endregion } // Once all rooms are placed, find all doors and try to connect a closet to each one. Fill it out a little. // If a door cannot have a closet attached, remove the door (set back to wall) List<FloorTile> doors = new List<FloorTile>(); foreach( Room r in rooms ) foreach( FloorTile d in r.doors ) if( d.type == FloorType.DoorClosed ) doors.Add( d ); foreach( FloorTile targetDoor in doors ) { if( (room = GenerateGenericRoom(rooms, CastleRoomType.Closet, targetDoor)) != null ) { // to list of existing rooms for next room to refernce doors from rooms.Add(room); // update tje grid[,] of placed tiles foreach( FloorTile cell in room.cells ) { if( cell.x==targetDoor.x && cell.y==targetDoor.y ) grid[cell.x, cell.y].type = FloorType.DoorOpen; else grid[cell.x, cell.y].type = cell.type; } } else { // couldn't attach a closet - change from door to wall (cosmetic, really) // // TODO - need to remove doors from rooms when being attached, both the new placed room and the room that was attached to. // Otherwise this removes all the currently connected doors... not sure how to tackle this yet. grid[ targetDoor.x, targetDoor.y].type = FloorType.Floor; } } //finally pass - close all doors that were opened as flags for( int i = 0; i < width * pathWidth * castleScale; i++ ) for( int j = 0; j < pathWidth * height * castleScale; j++ ) if( grid[i,j].type == FloorType.DoorOpen ) grid[i,j].type = FloorType.DoorClosed; //dlg.CanClose = true; // generation done - let the user click to close after admiring the nifty black&white for a bit. dlg.CloseProgressDialog(); return grid; }
private void OnPreviewPaint( object sender, PaintEventArgs e ) { if( flags == null ) return; if( pic == null ) { pic = new Bitmap( flags.GetLength( 0 ), flags.GetLength( 1 ) ); ClientSize = new Size( ClientRectangle.Width - pictureBox2.Width + pic.Width, ClientRectangle.Height- pictureBox2.Height + pic.Height ); } if( pic == null ) return; for( int i=0; i<pic.Width; i++ ) for( int j=0; j<pic.Height; j++ ) { if(flags[i, j].cameFrom==null) pic.SetPixel(i, j, flags[i, j].type==FloorType.Wall ? Color.Black : Color.White); else pic.SetPixel(i, j, flags[i, j].type==FloorType.Wall ? Color.Gray : Color.LightGray); } while( current != null ) { pic.SetPixel( current.x, current.y, Color.Blue ); current = current.cameFrom; } e.Graphics.DrawImageUnscaled( pic, 0, 0 ); if( start != null && end != null ) e.Graphics.DrawLine( Pens.Red, start.x, start.y, end.x, end.y ); }