BranchCountMode.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. using DunGen.Graph;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEngine;
  6. namespace DunGen
  7. {
  8. /// <summary>
  9. /// Used to determine how the number of branches are calculated
  10. /// </summary>
  11. public enum BranchMode
  12. {
  13. /// <summary>
  14. /// Branch count is calculated per-tile using the Archetype's BranchCount property
  15. /// </summary>
  16. Local,
  17. /// <summary>
  18. /// Branch count is calculated across the entire dungeon using the DungeonFlow's BranchCount property
  19. /// </summary>
  20. Global,
  21. /// <summary>
  22. /// Branch count is calculated for each section of the dungeon flow graph, using the Archetype's BranchCount property
  23. /// </summary>
  24. Section,
  25. }
  26. public static class BranchCountHelper
  27. {
  28. public static void ComputeBranchCounts(DungeonFlow dungeonFlow, RandomStream randomStream, DungeonProxy proxyDungeon, ref int[] mainPathBranches)
  29. {
  30. switch (dungeonFlow.BranchMode)
  31. {
  32. case BranchMode.Local:
  33. ComputeBranchCountsLocal(randomStream, proxyDungeon, ref mainPathBranches);
  34. break;
  35. case BranchMode.Global:
  36. ComputeBranchCountsGlobal(dungeonFlow, randomStream, proxyDungeon, ref mainPathBranches);
  37. break;
  38. case BranchMode.Section:
  39. ComputeBranchCountsPerSection(randomStream, proxyDungeon, ref mainPathBranches);
  40. break;
  41. default:
  42. throw new NotImplementedException(string.Format("{0}.{1} is not implemented", typeof(BranchMode).Name, dungeonFlow.BranchMode));
  43. }
  44. }
  45. private static void ComputeBranchCountsLocal(RandomStream randomStream, DungeonProxy proxyDungeon, ref int[] mainPathBranches)
  46. {
  47. for (int i = 0; i < mainPathBranches.Length; i++)
  48. {
  49. var tile = proxyDungeon.MainPathTiles[i];
  50. if (tile.Placement.Archetype == null)
  51. continue;
  52. int branchCount = tile.Placement.Archetype.BranchCount.GetRandom(randomStream);
  53. branchCount = Mathf.Min(branchCount, tile.UnusedDoorways.Count());
  54. mainPathBranches[i] = branchCount;
  55. }
  56. }
  57. private static void ComputeBranchCountsPerSection(RandomStream randomStream, DungeonProxy proxyDungeon, ref int[] mainPathBranches)
  58. {
  59. var sectionBranchCounts = new Dictionary<GraphLine, int>();
  60. // Determine how many branches should appear per section
  61. foreach (var tile in proxyDungeon.MainPathTiles)
  62. {
  63. var section = tile.Placement.GraphLine;
  64. if (section != null && !sectionBranchCounts.ContainsKey(section))
  65. {
  66. var archetype = tile.Placement.Archetype;
  67. sectionBranchCounts.Add(section, archetype.BranchCount.GetRandom(randomStream));
  68. }
  69. }
  70. // Distribute branches per-section
  71. foreach (var pair in sectionBranchCounts)
  72. {
  73. var section = pair.Key;
  74. int sectionBranchCount = pair.Value;
  75. // Find and cache the number of unused doorways there are per tile within this section
  76. var tileDoorwayCounts = new Dictionary<int, int>();
  77. for (int i = 0; i < proxyDungeon.MainPathTiles.Count; i++)
  78. {
  79. var tile = proxyDungeon.MainPathTiles[i];
  80. if (tile.Placement.GraphLine != section)
  81. continue;
  82. tileDoorwayCounts[i] = tile.UnusedDoorways.Count();
  83. }
  84. // Calculate the total number of unused doorways within this section
  85. int totalDoorwayCount = tileDoorwayCounts.Sum(x => x.Value);
  86. float[] tileWeights = new float[tileDoorwayCounts.Count];
  87. // Calculate a weight value for each tile in the section
  88. for (int i = 0; i < tileDoorwayCounts.Count; i++)
  89. {
  90. int mainPathIndex = tileDoorwayCounts.Keys.ElementAt(i);
  91. int tileDoorwayCount = tileDoorwayCounts.Values.ElementAt(i);
  92. // The proportion of branches that should belong to this tile
  93. float tileWeight = tileDoorwayCount / (float)totalDoorwayCount;
  94. tileWeights[i] = tileWeight;
  95. }
  96. // Distribute the branches for this section across all tiles within the section,
  97. // weighted by the number of available doorways each tile has
  98. int[] assignedDoorwayCounts = DistributeByWeights(sectionBranchCount, tileWeights);
  99. for (int i = 0; i < assignedDoorwayCounts.Length; i++)
  100. {
  101. int tileMainPathIndex = tileDoorwayCounts.Keys.ElementAt(i);
  102. mainPathBranches[tileMainPathIndex] = assignedDoorwayCounts[i];
  103. }
  104. }
  105. }
  106. private static void ComputeBranchCountsGlobal(DungeonFlow dungeonFlow, RandomStream randomStream, DungeonProxy proxyDungeon, ref int[] mainPathBranches)
  107. {
  108. int globalBranchCount = dungeonFlow.BranchCount.GetRandom(randomStream);
  109. int totalBranchableRooms = proxyDungeon.MainPathTiles.Count(t => t.Placement.Archetype != null && t.UnusedDoorways.Any());
  110. float branchesPerTile = globalBranchCount / (float)totalBranchableRooms;
  111. float branchChance = branchesPerTile;
  112. int branchesRemaining = globalBranchCount;
  113. for (int i = 0; i < mainPathBranches.Length; i++)
  114. {
  115. if (branchesRemaining <= 0)
  116. break;
  117. var tile = proxyDungeon.MainPathTiles[i];
  118. if (tile.Placement.Archetype == null || !tile.UnusedDoorways.Any())
  119. continue;
  120. int availableDoorways = tile.UnusedDoorways.Count();
  121. // Add as many guaranteed branches as needed when branchChance is > 100%
  122. int branchCount = Mathf.FloorToInt(branchChance);
  123. branchCount = Mathf.Min(branchCount, availableDoorways, tile.Placement.Archetype.BranchCount.Max, branchesRemaining);
  124. branchChance -= branchCount;
  125. availableDoorways -= branchCount;
  126. // Randomly choose to add a branch to this tile
  127. bool tileSupportsMoreBranches = branchCount < availableDoorways && branchCount < branchesRemaining;
  128. if (tileSupportsMoreBranches)
  129. {
  130. bool addNewBranch = randomStream.NextDouble() < branchChance;
  131. if (addNewBranch)
  132. {
  133. branchCount++;
  134. branchChance = 0f;
  135. }
  136. }
  137. branchChance += branchesPerTile;
  138. branchesRemaining -= branchCount;
  139. mainPathBranches[i] = branchCount;
  140. }
  141. }
  142. /// <summary>
  143. /// Distributes a whole number of elements into discrete chunks based on provided per-chunk weights
  144. /// </summary>
  145. /// <param name="count">The number of things to distribute</param>
  146. /// <param name="weights">The weight values for each bucket</param>
  147. /// <returns>The number of elements per bucket</returns>
  148. private static int[] DistributeByWeights(int count, float[] weights)
  149. {
  150. int Round(double x)
  151. {
  152. return (int)(x >= 0 ? x + 0.5 : x - 0.5);
  153. };
  154. if (weights == null)
  155. throw new ArgumentNullException(nameof(weights));
  156. else if (weights.Length <= 0)
  157. throw new ArgumentOutOfRangeException(nameof(weights), "Empty weights");
  158. double sum = weights.Sum(x => (double)x);
  159. if (sum == 0)
  160. throw new ArgumentException("Weights must not sum to 0", nameof(weights));
  161. int[] result = new int[weights.Length];
  162. double diff = 0;
  163. for (int i = 0; i < weights.Length; ++i)
  164. {
  165. double v = count * (double)(weights[i]) / sum;
  166. int value = Round(v);
  167. diff += v - value;
  168. if (diff >= 0.5)
  169. {
  170. value += 1;
  171. diff -= 1;
  172. }
  173. else if (diff <= -0.5)
  174. {
  175. value -= 1;
  176. diff += 1;
  177. }
  178. result[i] = value;
  179. }
  180. return result;
  181. }
  182. }
  183. }