/// <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; }