using DunGen.Graph; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace DunGen { /// /// Used to determine how the number of branches are calculated /// public enum BranchMode { /// /// Branch count is calculated per-tile using the Archetype's BranchCount property /// Local, /// /// Branch count is calculated across the entire dungeon using the DungeonFlow's BranchCount property /// Global, /// /// Branch count is calculated for each section of the dungeon flow graph, using the Archetype's BranchCount property /// Section, } public static class BranchCountHelper { public static void ComputeBranchCounts(DungeonFlow dungeonFlow, RandomStream randomStream, DungeonProxy proxyDungeon, ref int[] mainPathBranches) { switch (dungeonFlow.BranchMode) { case BranchMode.Local: ComputeBranchCountsLocal(randomStream, proxyDungeon, ref mainPathBranches); break; case BranchMode.Global: ComputeBranchCountsGlobal(dungeonFlow, randomStream, proxyDungeon, ref mainPathBranches); break; case BranchMode.Section: ComputeBranchCountsPerSection(randomStream, proxyDungeon, ref mainPathBranches); break; default: throw new NotImplementedException(string.Format("{0}.{1} is not implemented", typeof(BranchMode).Name, dungeonFlow.BranchMode)); } } private static void ComputeBranchCountsLocal(RandomStream randomStream, DungeonProxy proxyDungeon, ref int[] mainPathBranches) { for (int i = 0; i < mainPathBranches.Length; i++) { var tile = proxyDungeon.MainPathTiles[i]; if (tile.Placement.Archetype == null) continue; int branchCount = tile.Placement.Archetype.BranchCount.GetRandom(randomStream); branchCount = Mathf.Min(branchCount, tile.UnusedDoorways.Count()); mainPathBranches[i] = branchCount; } } private static void ComputeBranchCountsPerSection(RandomStream randomStream, DungeonProxy proxyDungeon, ref int[] mainPathBranches) { var sectionBranchCounts = new Dictionary(); // Determine how many branches should appear per section foreach (var tile in proxyDungeon.MainPathTiles) { var section = tile.Placement.GraphLine; if (section != null && !sectionBranchCounts.ContainsKey(section)) { var archetype = tile.Placement.Archetype; sectionBranchCounts.Add(section, archetype.BranchCount.GetRandom(randomStream)); } } // Distribute branches per-section foreach (var pair in sectionBranchCounts) { var section = pair.Key; int sectionBranchCount = pair.Value; // Find and cache the number of unused doorways there are per tile within this section var tileDoorwayCounts = new Dictionary(); for (int i = 0; i < proxyDungeon.MainPathTiles.Count; i++) { var tile = proxyDungeon.MainPathTiles[i]; if (tile.Placement.GraphLine != section) continue; tileDoorwayCounts[i] = tile.UnusedDoorways.Count(); } // Calculate the total number of unused doorways within this section int totalDoorwayCount = tileDoorwayCounts.Sum(x => x.Value); float[] tileWeights = new float[tileDoorwayCounts.Count]; // Calculate a weight value for each tile in the section for (int i = 0; i < tileDoorwayCounts.Count; i++) { int mainPathIndex = tileDoorwayCounts.Keys.ElementAt(i); int tileDoorwayCount = tileDoorwayCounts.Values.ElementAt(i); // The proportion of branches that should belong to this tile float tileWeight = tileDoorwayCount / (float)totalDoorwayCount; tileWeights[i] = tileWeight; } // Distribute the branches for this section across all tiles within the section, // weighted by the number of available doorways each tile has int[] assignedDoorwayCounts = DistributeByWeights(sectionBranchCount, tileWeights); for (int i = 0; i < assignedDoorwayCounts.Length; i++) { int tileMainPathIndex = tileDoorwayCounts.Keys.ElementAt(i); mainPathBranches[tileMainPathIndex] = assignedDoorwayCounts[i]; } } } private static void ComputeBranchCountsGlobal(DungeonFlow dungeonFlow, RandomStream randomStream, DungeonProxy proxyDungeon, ref int[] mainPathBranches) { int globalBranchCount = dungeonFlow.BranchCount.GetRandom(randomStream); int totalBranchableRooms = proxyDungeon.MainPathTiles.Count(t => t.Placement.Archetype != null && t.UnusedDoorways.Any()); float branchesPerTile = globalBranchCount / (float)totalBranchableRooms; float branchChance = branchesPerTile; int branchesRemaining = globalBranchCount; for (int i = 0; i < mainPathBranches.Length; i++) { if (branchesRemaining <= 0) break; var tile = proxyDungeon.MainPathTiles[i]; if (tile.Placement.Archetype == null || !tile.UnusedDoorways.Any()) continue; int availableDoorways = tile.UnusedDoorways.Count(); // Add as many guaranteed branches as needed when branchChance is > 100% int branchCount = Mathf.FloorToInt(branchChance); branchCount = Mathf.Min(branchCount, availableDoorways, tile.Placement.Archetype.BranchCount.Max, branchesRemaining); branchChance -= branchCount; availableDoorways -= branchCount; // Randomly choose to add a branch to this tile bool tileSupportsMoreBranches = branchCount < availableDoorways && branchCount < branchesRemaining; if (tileSupportsMoreBranches) { bool addNewBranch = randomStream.NextDouble() < branchChance; if (addNewBranch) { branchCount++; branchChance = 0f; } } branchChance += branchesPerTile; branchesRemaining -= branchCount; mainPathBranches[i] = branchCount; } } /// /// Distributes a whole number of elements into discrete chunks based on provided per-chunk weights /// /// The number of things to distribute /// The weight values for each bucket /// The number of elements per bucket private static int[] DistributeByWeights(int count, float[] weights) { int Round(double x) { return (int)(x >= 0 ? x + 0.5 : x - 0.5); }; if (weights == null) throw new ArgumentNullException(nameof(weights)); else if (weights.Length <= 0) throw new ArgumentOutOfRangeException(nameof(weights), "Empty weights"); double sum = weights.Sum(x => (double)x); if (sum == 0) throw new ArgumentException("Weights must not sum to 0", nameof(weights)); int[] result = new int[weights.Length]; double diff = 0; for (int i = 0; i < weights.Length; ++i) { double v = count * (double)(weights[i]) / sum; int value = Round(v); diff += v - value; if (diff >= 0.5) { value += 1; diff -= 1; } else if (diff <= -0.5) { value -= 1; diff += 1; } result[i] = value; } return result; } } }