/// <inheritdoc /> protected override IEnumerator <object?> OnPerform(GenerationContext context) { // Validate configuration if (CutoffBigAreaFill > TotalIterations) { throw new InvalidConfigurationException(this, nameof(CutoffBigAreaFill), $"The value must be less than or equal to the value of {nameof(TotalIterations)}."); } // Get or create/add a wall-floor context component var wallFloorContext = context.GetFirstOrNew <ISettableGridView <bool> >( () => new ArrayView <bool>(context.Width, context.Height), WallFloorComponentTag); // Create a new array map to use in the smoothing algorithms to temporarily store old values. // Allocating it here instead of in the smoothing minimizes allocations. var oldMap = new ArrayView <bool>(wallFloorContext.Width, wallFloorContext.Height); // Iterate over the generated values, smoothing them with the appropriate algorithm for (int i = 0; i < TotalIterations; i++) { CellAutoSmoothingAlgo(wallFloorContext, oldMap, i < CutoffBigAreaFill); yield return(null); } // Fill to a rectangle to ensure the resulting areas are enclosed foreach (var pos in wallFloorContext.Bounds().PerimeterPositions()) { wallFloorContext[pos] = false; } }
/// <inheritdoc/> protected override IEnumerator <object?> OnPerform(GenerationContext context) { // Get or create/add a wall-floor context component var wallFloorContext = context.GetFirstOrNew <ISettableGridView <bool> >( () => new ArrayView <bool>(context.Width, context.Height), WallFloorComponentTag ); var innerBounds = wallFloorContext.Bounds().Expand(-1, -1); foreach (var position in wallFloorContext.Positions()) { wallFloorContext[position] = innerBounds.Contains(position); } // No stages as its a simple rectangle generator yield break; }
/// <inheritdoc/> protected override IEnumerator <object?> OnPerform(GenerationContext context) { // Validate configuration if (FillProbability > 100) { throw new InvalidConfigurationException(this, nameof(FillProbability), "The value must be a valid percent (between 0 and 100)."); } // Get or create/add a grid view context component to fill var gridViewContext = context.GetFirstOrNew <ISettableGridView <bool> >( () => new ArrayView <bool>(context.Width, context.Height), GridViewComponentTag); // Determine positions to fill based on exclusion settings var positionsRect = ExcludePerimeterPoints ? gridViewContext.Bounds().Expand(-1, -1) : gridViewContext.Bounds(); // Fill each position with a random value uint squares = 0; foreach (var position in positionsRect.Positions()) { gridViewContext[position] = RNG.PercentageCheck(FillProbability); squares++; if (FillsBetweenPauses != 0 && squares == FillsBetweenPauses) { squares = 0; yield return(null); } } // Pause one last time if we need if (FillsBetweenPauses != 0 && squares != 0) { yield return(null); } }
/// <inheritdoc /> protected override IEnumerator <object?> OnPerform(GenerationContext context) { // Validate configuration if (MinRooms > MaxRooms) { throw new InvalidConfigurationException(this, nameof(MinRooms), $"The value must be less than or equal to the value of {nameof(MaxRooms)}."); } if (RoomMinSize > RoomMaxSize) { throw new InvalidConfigurationException(this, nameof(RoomMinSize), $"The value must be less than or equal to the value of ${nameof(RoomMaxSize)}."); } if (RoomSizeRatioX <= 0f) { throw new InvalidConfigurationException(this, nameof(RoomSizeRatioX), "The value must be greater than 0."); } if (RoomSizeRatioY <= 0f) { throw new InvalidConfigurationException(this, nameof(RoomSizeRatioY), "The value must be greater than 0."); } // Get or create/add a wall-floor context component var wallFloorContext = context.GetFirstOrNew <ISettableGridView <bool> >( () => new ArrayView <bool>(context.Width, context.Height), WallFloorComponentTag ); // Determine how many rooms to generate var roomCounter = RNG.NextInt(MinRooms, MaxRooms + 1); // Get or create/add a rooms context component var roomsContext = context.GetFirstOrNew( () => new ItemList <Rectangle>(roomCounter), RoomsComponentTag ); // Try to place all the rooms while (roomCounter != 0) { var tryCounterCreate = MaxCreationAttempts; var placed = false; // Attempt to create the room until either we reach max attempts or we create and place a room in a valid location while (tryCounterCreate != 0) { var roomSize = RNG.NextInt(RoomMinSize, RoomMaxSize + 1); var width = (int)(roomSize * RoomSizeRatioX); // This helps with non square fonts. So rooms don't look odd var height = (int)(roomSize * RoomSizeRatioY); // When accounting for font ratios, these adjustments help prevent all rooms // having the same looking square format var adjustmentBase = roomSize / 4; if (adjustmentBase != 0) { var adjustment = RNG.NextInt(-adjustmentBase, adjustmentBase + 1); var adjustmentChance = RNG.NextInt(0, 2); if (adjustmentChance == 0) { width += (int)(adjustment * RoomSizeRatioX); } else if (adjustmentChance == 1) { height += (int)(adjustment * RoomSizeRatioY); } } width = Math.Max(RoomMinSize, width); height = Math.Max(RoomMinSize, height); // Keep room interior odd, helps with placement + tunnels around the outside. if (width % 2 == 0) { width += 1; } if (height % 2 == 0) { height += 1; } var roomInnerRect = new Rectangle(0, 0, width, height); var tryCounterPlace = MaxPlacementAttempts; // Try to place the room we've created until either it doesn't intersect any other rooms, or we reach max retries (in which case, we will scrap the room entirely, create a new one, and try again) while (tryCounterPlace != 0) { int xPos = 0, yPos = 0; // Generate the rooms at odd positions, to make door/tunnel placement easier while (xPos % 2 == 0) { xPos = RNG.NextInt(3, wallFloorContext.Width - roomInnerRect.Width - 3); } while (yPos % 2 == 0) { yPos = RNG.NextInt(3, wallFloorContext.Height - roomInnerRect.Height - 3); } // Record a rectangle for the inner and outer bounds of the room we've created roomInnerRect = roomInnerRect.WithPosition(new Point(xPos, yPos)); var roomBounds = roomInnerRect.Expand(3, 3); // Check if the room intersects with any floor tile on the map already. We do it this way instead of checking against only the rooms list // to ensure that if some other map generation step placed things before we did, we don't intersect those. var intersected = false; foreach (var point in roomBounds.Positions()) { if (wallFloorContext[point]) { intersected = true; break; } } // If we intersected floor tiles, try to place the room again if (intersected) { tryCounterPlace--; continue; } // Once we place it in a valid location, update the wall/floor context, and add the room to the list of rooms. foreach (var point in roomInnerRect.Positions()) { wallFloorContext[point] = true; } placed = true; roomsContext.Add(roomInnerRect, Name); break; } if (placed) { yield return(null); break; } tryCounterCreate--; } roomCounter--; } }
/// <inheritdoc /> protected override IEnumerator <object?> OnPerform(GenerationContext context) { // Validate configuration if (CrawlerChangeDirectionImprovement > 100) { throw new InvalidConfigurationException(this, nameof(CrawlerChangeDirectionImprovement), "The value must be a valid percent (between 0 and 100)."); } // Logic implemented from http://journal.stuffwithstuff.com/2014/12/21/rooms-and-mazes/ // Get or create/add a wall-floor context component var wallFloorContext = context.GetFirstOrNew <ISettableGridView <bool> >( () => new ArrayView <bool>(context.Width, context.Height), WallFloorComponentTag ); // Get or create/add a tunnel list context component var tunnelList = context.GetFirstOrNew( () => new ItemList <Area>(), TunnelsComponentTag ); // Record spaces we've crawled to introduce changes. int spacesCrawled = 0; var crawlers = new List <Crawler>(); var empty = FindEmptySquare(wallFloorContext, RNG); while (empty != Point.None) { var crawler = new Crawler(); crawlers.Add(crawler); crawler.MoveTo(empty); var startedCrawler = true; ushort percentChangeDirection = 0; while (crawler.Path.Count != 0) { // Dig this position wallFloorContext[crawler.CurrentPosition] = true; // Get valid directions (basically is any position outside the map or not? var points = AdjacencyRule.Cardinals.NeighborsClockwise(crawler.CurrentPosition).ToArray(); var directions = AdjacencyRule.Cardinals.DirectionsOfNeighborsClockwise(Direction.None).ToList(); var validDirections = new bool[4]; // Rule out any valid directions based on their position. Only process cardinals, do not use diagonals for (var i = 0; i < 4; i++) { validDirections[i] = IsPointWallsExceptSource(wallFloorContext, points[i], directions[i] + 4); } // If not a new crawler, exclude where we came from if (!startedCrawler) { validDirections[directions.IndexOf(crawler.Facing + 4)] = false; } // Do we have any valid direction to go? if (validDirections[0] || validDirections[1] || validDirections[2] || validDirections[3]) { int index; // Are we just starting this crawler? OR Is the current crawler facing // direction invalid? if (startedCrawler || validDirections[directions.IndexOf(crawler.Facing)] == false) { // Just get anything index = GetDirectionIndex(validDirections, RNG); crawler.Facing = directions[index]; percentChangeDirection = 0; startedCrawler = false; } else { // Increase probability we change direction percentChangeDirection += CrawlerChangeDirectionImprovement; if (RNG.PercentageCheck(percentChangeDirection)) { index = GetDirectionIndex(validDirections, RNG); crawler.Facing = directions[index]; percentChangeDirection = 0; } else { index = directions.IndexOf(crawler.Facing); } } crawler.MoveTo(points[index]); spacesCrawled++; } else { crawler.Backtrack(); spacesCrawled++; } if (spacesCrawled >= 10) { yield return(null); spacesCrawled = 0; } } if (spacesCrawled > 0) { yield return(null); spacesCrawled = 0; } empty = FindEmptySquare(wallFloorContext, RNG); } // Add appropriate items to the tunnels list tunnelList.AddRange(crawlers.Select(c => c.AllPositions).Where(a => a.Count != 0), Name); }