using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using DunGen.Graph; using UnityEngine; using UnityEngine.Serialization; namespace DunGen { [Serializable] public class DungeonGenerator : ISerializationCallbackReceiver { public const int CurrentFileVersion = 1; [SerializeField] [FormerlySerializedAs("AllowImmediateRepeats")] private bool allowImmediateRepeats; public int Seed; public bool ShouldRandomizeSeed = true; public int MaxAttemptCount = 20; public bool UseMaximumPairingAttempts; public int MaxPairingAttempts = 5; public bool IgnoreSpriteBounds; public AxisDirection UpDirection = AxisDirection.PosY; [FormerlySerializedAs("OverrideAllowImmediateRepeats")] public bool OverrideRepeatMode; public TileRepeatMode RepeatMode; public bool OverrideAllowTileRotation; public bool AllowTileRotation; public bool DebugRender; public float LengthMultiplier = 1f; public bool PlaceTileTriggers = true; public int TileTriggerLayer = 2; public bool GenerateAsynchronously; public float MaxAsyncFrameMilliseconds = 50f; public float PauseBetweenRooms; public bool RestrictDungeonToBounds; public Bounds TilePlacementBounds = new Bounds(Vector3.zero, Vector3.one * 10f); public float OverlapThreshold = 0.01f; public float Padding; public bool DisallowOverhangs; public GameObject Root; public DungeonFlow DungeonFlow; protected int retryCount; protected DungeonProxy proxyDungeon; protected readonly Dictionary tilePlacementResultCounters = new Dictionary(); protected readonly List useableTiles = new List(); protected int targetLength; protected List tilesPendingInjection; protected List postProcessSteps = new List(); [SerializeField] private int fileVersion; private int nextNodeIndex; private DungeonArchetype currentArchetype; private GraphLine previousLineSegment; private List preProcessData = new List(); private Stopwatch yieldTimer = new Stopwatch(); private Dictionary injectedTiles = new Dictionary(); public RandomStream RandomStream { get; protected set; } public Vector3 UpVector => UpDirection switch { AxisDirection.PosX => new Vector3(1f, 0f, 0f), AxisDirection.NegX => new Vector3(-1f, 0f, 0f), AxisDirection.PosY => new Vector3(0f, 1f, 0f), AxisDirection.NegY => new Vector3(0f, -1f, 0f), AxisDirection.PosZ => new Vector3(0f, 0f, 1f), AxisDirection.NegZ => new Vector3(0f, 0f, -1f), _ => throw new NotImplementedException("AxisDirection '" + UpDirection.ToString() + "' not implemented"), }; public GenerationStatus Status { get; private set; } public GenerationStats GenerationStats { get; private set; } public int ChosenSeed { get; protected set; } public Dungeon CurrentDungeon { get; private set; } public bool IsGenerating { get; private set; } public bool IsAnalysis { get; set; } public event GenerationStatusDelegate OnGenerationStatusChanged; public static event GenerationStatusDelegate OnAnyDungeonGenerationStatusChanged; public event TileInjectionDelegate TileInjectionMethods; public event Action Cleared; public event Action Retrying; public DungeonGenerator() { GenerationStats = new GenerationStats(); } public DungeonGenerator(GameObject root) : this() { Root = root; } public void Generate() { if (!IsGenerating) { IsAnalysis = false; IsGenerating = true; Wait(OuterGenerate()); } } public void Cancel() { if (IsGenerating) { Clear(stopCoroutines: true); IsGenerating = false; } } public Dungeon DetachDungeon() { if (CurrentDungeon == null) { return null; } Dungeon currentDungeon = CurrentDungeon; CurrentDungeon = null; Root = null; Clear(stopCoroutines: true); return currentDungeon; } protected virtual IEnumerator OuterGenerate() { Clear(stopCoroutines: false); yieldTimer.Restart(); Status = GenerationStatus.NotStarted; ChosenSeed = (ShouldRandomizeSeed ? new RandomStream().Next() : Seed); RandomStream = new RandomStream(ChosenSeed); if (Root == null) { Root = new GameObject("Dungeon"); } yield return Wait(InnerGenerate(isRetry: false)); IsGenerating = false; } private Coroutine Wait(IEnumerator routine) { if (GenerateAsynchronously) { return CoroutineHelper.Start(routine); } while (routine.MoveNext()) { } return null; } public void RandomizeSeed() { Seed = new RandomStream().Next(); } protected virtual IEnumerator InnerGenerate(bool isRetry) { if (isRetry) { ChosenSeed = RandomStream.Next(); RandomStream = new RandomStream(ChosenSeed); if (retryCount >= MaxAttemptCount && Application.isEditor) { string text = "Failed to generate the dungeon " + MaxAttemptCount + " times.\nThis could indicate a problem with the way the tiles are set up. Try to make sure most rooms have more than one doorway and that all doorways are easily accessible.\nHere are a list of all reasons a tile placement had to be retried:"; foreach (KeyValuePair tilePlacementResultCounter in tilePlacementResultCounters) { if (tilePlacementResultCounter.Value > 0) { text = text + "\n" + tilePlacementResultCounter.Key.ToString() + " (x" + tilePlacementResultCounter.Value + ")"; } } UnityEngine.Debug.LogError(text); ChangeStatus(GenerationStatus.Failed); yield break; } retryCount++; GenerationStats.IncrementRetryCount(); if (this.Retrying != null) { this.Retrying(); } } else { retryCount = 0; GenerationStats.Clear(); } CurrentDungeon = Root.GetComponent(); if (CurrentDungeon == null) { CurrentDungeon = Root.AddComponent(); } CurrentDungeon.DebugRender = DebugRender; CurrentDungeon.PreGenerateDungeon(this); Clear(stopCoroutines: false); targetLength = Mathf.RoundToInt((float)DungeonFlow.Length.GetRandom(RandomStream) * LengthMultiplier); targetLength = Mathf.Max(targetLength, 2); Transform debugVisualsRoot = ((PauseBetweenRooms > 0f) ? Root.transform : null); proxyDungeon = new DungeonProxy(debugVisualsRoot); GenerationStats.BeginTime(GenerationStatus.TileInjection); if (tilesPendingInjection == null) { tilesPendingInjection = new List(); } else { tilesPendingInjection.Clear(); } injectedTiles.Clear(); GatherTilesToInject(); GenerationStats.BeginTime(GenerationStatus.PreProcessing); PreProcess(); GenerationStats.BeginTime(GenerationStatus.MainPath); yield return Wait(GenerateMainPath()); if (Status == GenerationStatus.Complete || Status == GenerationStatus.Failed) { yield break; } GenerationStats.BeginTime(GenerationStatus.Branching); yield return Wait(GenerateBranchPaths()); foreach (InjectedTile item in tilesPendingInjection) { if (item.IsRequired) { yield return Wait(InnerGenerate(isRetry: true)); yield break; } } if (Status == GenerationStatus.Complete || Status == GenerationStatus.Failed) { yield break; } if (DungeonFlow.BranchPruneTags.Count > 0) { PruneBranches(); } proxyDungeon.ConnectOverlappingDoorways(DungeonFlow.DoorwayConnectionChance, DungeonFlow, RandomStream); CurrentDungeon.FromProxy(proxyDungeon, this); yield return Wait(PostProcess()); yield return null; IDungeonCompleteReceiver[] componentsInChildren = CurrentDungeon.gameObject.GetComponentsInChildren(includeInactive: false); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].OnDungeonComplete(CurrentDungeon); } ChangeStatus(GenerationStatus.Complete); if (true) { DungenCharacter[] array = UnityEngine.Object.FindObjectsOfType(); for (int i = 0; i < array.Length; i++) { array[i].ForceRecheckTile(); } } } private void PruneBranches() { Stack stack = new Stack(); foreach (TileProxy tile2 in proxyDungeon.BranchPathTiles) { if (!tile2.UsedDoorways.Select((DoorwayProxy d) => d.ConnectedDoorway.TileProxy).Any((TileProxy t) => t.Placement.BranchDepth > tile2.Placement.BranchDepth)) { stack.Push(tile2); } } while (stack.Count > 0) { TileProxy tile = stack.Pop(); if ((tile.Placement.InjectionData == null || !tile.Placement.InjectionData.IsRequired) && DungeonFlow.ShouldPruneTileWithTags(tile.PrefabTile.Tags)) { ProxyDoorwayConnection connection = (from d in tile.UsedDoorways select d.ConnectedDoorway into d where d.TileProxy.Placement.IsOnMainPath || d.TileProxy.Placement.BranchDepth < tile.Placement.BranchDepth select new ProxyDoorwayConnection(d, d.ConnectedDoorway)).First(); proxyDungeon.RemoveTile(tile); proxyDungeon.RemoveConnection(connection); GenerationStats.PrunedBranchTileCount++; TileProxy tileProxy = connection.A.TileProxy; if (!tileProxy.Placement.IsOnMainPath) { stack.Push(tileProxy); } } } } public virtual void Clear(bool stopCoroutines) { if (stopCoroutines) { CoroutineHelper.StopAll(); } if (proxyDungeon != null) { proxyDungeon.ClearDebugVisuals(); } proxyDungeon = null; if (CurrentDungeon != null) { CurrentDungeon.Clear(); } useableTiles.Clear(); preProcessData.Clear(); previousLineSegment = null; tilePlacementResultCounters.Clear(); if (this.Cleared != null) { this.Cleared(); } } private void ChangeStatus(GenerationStatus status) { GenerationStatus status2 = Status; Status = status; if (status == GenerationStatus.Complete || status == GenerationStatus.Failed) { IsGenerating = false; } if (status == GenerationStatus.Failed) { Clear(stopCoroutines: true); } if (status2 != status) { this.OnGenerationStatusChanged?.Invoke(this, status); DungeonGenerator.OnAnyDungeonGenerationStatusChanged?.Invoke(this, status); } } protected virtual void PreProcess() { if (preProcessData.Count > 0) { return; } ChangeStatus(GenerationStatus.PreProcessing); foreach (TileSet item in DungeonFlow.GetUsedTileSets().Concat(tilesPendingInjection.Select((InjectedTile x) => x.TileSet)).Distinct()) { foreach (GameObjectChance weight in item.TileWeights.Weights) { if (weight.Value != null) { useableTiles.Add(weight.Value); weight.TileSet = item; } } } } protected virtual void GatherTilesToInject() { RandomStream randomStream = new RandomStream(ChosenSeed); foreach (TileInjectionRule tileInjectionRule in DungeonFlow.TileInjectionRules) { if (!(tileInjectionRule.TileSet == null) && (tileInjectionRule.CanAppearOnMainPath || tileInjectionRule.CanAppearOnBranchPath)) { bool isOnMainPath = !tileInjectionRule.CanAppearOnBranchPath || (tileInjectionRule.CanAppearOnMainPath && randomStream.NextDouble() > 0.5); InjectedTile item = new InjectedTile(tileInjectionRule, isOnMainPath, randomStream); tilesPendingInjection.Add(item); } } if (this.TileInjectionMethods != null) { this.TileInjectionMethods(randomStream, ref tilesPendingInjection); } } protected virtual IEnumerator GenerateMainPath() { ChangeStatus(GenerationStatus.MainPath); nextNodeIndex = 0; List list = new List(DungeonFlow.Nodes.Count); bool flag = false; int num = 0; List> tileSets = new List>(targetLength); List archetypes = new List(targetLength); List nodes = new List(targetLength); List lines = new List(targetLength); while (!flag) { float num2 = Mathf.Clamp((float)num / (float)(targetLength - 1), 0f, 1f); GraphLine lineAtDepth = DungeonFlow.GetLineAtDepth(num2); if (lineAtDepth == null) { yield return Wait(InnerGenerate(isRetry: true)); yield break; } if (lineAtDepth != previousLineSegment) { currentArchetype = lineAtDepth.GetRandomArchetype(RandomStream, archetypes); previousLineSegment = lineAtDepth; } GraphNode graphNode = null; GraphNode[] array = DungeonFlow.Nodes.OrderBy((GraphNode x) => x.Position).ToArray(); GraphNode[] array2 = array; foreach (GraphNode graphNode2 in array2) { if (num2 >= graphNode2.Position && !list.Contains(graphNode2)) { graphNode = graphNode2; list.Add(graphNode2); break; } } List tileSets2; if (graphNode != null) { tileSets2 = graphNode.TileSets; nextNodeIndex = ((nextNodeIndex >= array.Length - 1) ? (-1) : (nextNodeIndex + 1)); archetypes.Add(null); lines.Add(null); nodes.Add(graphNode); if (graphNode == array[^1]) { flag = true; } } else { tileSets2 = currentArchetype.TileSets; archetypes.Add(currentArchetype); lines.Add(lineAtDepth); nodes.Add(null); } tileSets.Add(tileSets2); num++; } int tileRetryCount = 0; int totalForLoopRetryCount = 0; for (int i = 0; i < tileSets.Count; i++) { TileProxy attachTo = ((i == 0) ? null : proxyDungeon.MainPathTiles[proxyDungeon.MainPathTiles.Count - 1]); TileProxy tileProxy = AddTile(attachTo, tileSets[i], (float)i / (float)(tileSets.Count - 1), archetypes[i]); if (i > 5 && tileProxy == null && tileRetryCount < 5 && totalForLoopRetryCount < 20) { TileProxy tileProxy2 = proxyDungeon.MainPathTiles[i - 1]; if (injectedTiles.TryGetValue(tileProxy2, out var value)) { tilesPendingInjection.Add(value); injectedTiles.Remove(tileProxy2); } proxyDungeon.RemoveLastConnection(); proxyDungeon.RemoveTile(tileProxy2); i -= 2; tileRetryCount++; totalForLoopRetryCount++; } else { if (tileProxy == null) { yield return Wait(InnerGenerate(isRetry: true)); break; } tileProxy.Placement.GraphNode = nodes[i]; tileProxy.Placement.GraphLine = lines[i]; tileRetryCount = 0; if (ShouldSkipFrame(isRoomPlacement: true)) { yield return GetRoomPause(); } } } } private bool ShouldSkipFrame(bool isRoomPlacement) { if (!GenerateAsynchronously) { return false; } if (isRoomPlacement && PauseBetweenRooms > 0f) { return true; } if (yieldTimer.Elapsed.TotalMilliseconds >= (double)MaxAsyncFrameMilliseconds) { yieldTimer.Restart(); return true; } return false; } private YieldInstruction GetRoomPause() { if (PauseBetweenRooms > 0f) { return new WaitForSeconds(PauseBetweenRooms); } return null; } protected virtual IEnumerator GenerateBranchPaths() { ChangeStatus(GenerationStatus.Branching); int[] mainPathBranches = new int[proxyDungeon.MainPathTiles.Count]; BranchCountHelper.ComputeBranchCounts(DungeonFlow, RandomStream, proxyDungeon, ref mainPathBranches); for (int b = 0; b < mainPathBranches.Length; b++) { TileProxy tile = proxyDungeon.MainPathTiles[b]; int branchCount = mainPathBranches[b]; if (tile.Placement.Archetype == null || branchCount == 0) { continue; } for (int i = 0; i < branchCount; i++) { TileProxy previousTile = tile; int branchDepth = tile.Placement.Archetype.BranchingDepth.GetRandom(RandomStream); for (int j = 0; j < branchDepth; j++) { List useableTileSets = ((j != branchDepth - 1 || !tile.Placement.Archetype.GetHasValidBranchCapTiles()) ? tile.Placement.Archetype.TileSets : ((tile.Placement.Archetype.BranchCapType != 0) ? tile.Placement.Archetype.TileSets.Concat(tile.Placement.Archetype.BranchCapTileSets).ToList() : tile.Placement.Archetype.BranchCapTileSets)); float num = ((branchDepth <= 1) ? 1f : ((float)j / (float)(branchDepth - 1))); TileProxy tileProxy = AddTile(previousTile, useableTileSets, num, tile.Placement.Archetype); if (tileProxy == null) { break; } tileProxy.Placement.BranchDepth = j; tileProxy.Placement.NormalizedBranchDepth = num; tileProxy.Placement.GraphNode = previousTile.Placement.GraphNode; tileProxy.Placement.GraphLine = previousTile.Placement.GraphLine; previousTile = tileProxy; if (ShouldSkipFrame(isRoomPlacement: true)) { yield return GetRoomPause(); } } } } } protected virtual TileProxy AddTile(TileProxy attachTo, IEnumerable useableTileSets, float normalizedDepth, DungeonArchetype archetype, TilePlacementResult result = TilePlacementResult.None) { bool flag = Status == GenerationStatus.MainPath; bool flag2 = attachTo == null; InjectedTile injectedTile = null; int index = -1; bool flag3 = flag && archetype == null; if (tilesPendingInjection != null && !flag3) { float pathDepth = (flag ? normalizedDepth : ((float)attachTo.Placement.PathDepth / ((float)targetLength - 1f))); float branchDepth = (flag ? 0f : normalizedDepth); for (int i = 0; i < tilesPendingInjection.Count; i++) { InjectedTile injectedTile2 = tilesPendingInjection[i]; if (injectedTile2.ShouldInjectTileAtPoint(flag, pathDepth, branchDepth)) { injectedTile = injectedTile2; index = i; break; } } } IEnumerable collection = ((injectedTile == null) ? useableTileSets.SelectMany((TileSet x) => x.TileWeights.Weights) : new List(injectedTile.TileSet.TileWeights.Weights)); bool value = !flag2 && attachTo.PrefabTile.AllowRotation; if (OverrideAllowTileRotation) { value = AllowTileRotation; } DoorwayPairFinder obj = new DoorwayPairFinder { DungeonFlow = DungeonFlow, RandomStream = RandomStream, Archetype = archetype, GetTileTemplateDelegate = GetTileTemplate, IsOnMainPath = flag, NormalizedDepth = normalizedDepth, PreviousTile = attachTo, UpVector = UpVector, AllowRotation = value, TileWeights = new List(collection), IsTileAllowedPredicate = delegate(TileProxy previousTile, TileProxy potentialNextTile, ref float weight) { bool flag4 = previousTile != null && potentialNextTile.Prefab == previousTile.Prefab; TileRepeatMode tileRepeatMode = TileRepeatMode.Allow; if (OverrideRepeatMode) { tileRepeatMode = RepeatMode; } else if (potentialNextTile != null) { tileRepeatMode = potentialNextTile.PrefabTile.RepeatMode; } bool flag5 = true; return tileRepeatMode switch { TileRepeatMode.Allow => true, TileRepeatMode.DisallowImmediate => !flag4, TileRepeatMode.Disallow => !proxyDungeon.AllTiles.Where((TileProxy t) => t.Prefab == potentialNextTile.Prefab).Any(), _ => throw new NotImplementedException("TileRepeatMode " + tileRepeatMode.ToString() + " is not implemented"), }; } }; int? maxCount = (UseMaximumPairingAttempts ? new int?(MaxPairingAttempts) : null); Queue doorwayPairs = obj.GetDoorwayPairs(maxCount); TilePlacementResult tilePlacementResult = TilePlacementResult.NoValidTile; TileProxy tile = null; while (doorwayPairs.Count > 0) { DoorwayPair pair = doorwayPairs.Dequeue(); tilePlacementResult = TryPlaceTile(pair, archetype, out tile); if (tilePlacementResult == TilePlacementResult.None) { break; } AddTilePlacementResult(tilePlacementResult); } if (tilePlacementResult == TilePlacementResult.None) { if (injectedTile != null) { tile.Placement.InjectionData = injectedTile; injectedTiles[tile] = injectedTile; tilesPendingInjection.RemoveAt(index); if (flag) { targetLength++; } } return tile; } return null; } protected void AddTilePlacementResult(TilePlacementResult result) { if (!tilePlacementResultCounters.TryGetValue(result, out var value)) { tilePlacementResultCounters[result] = 1; } else { tilePlacementResultCounters[result] = value + 1; } } protected TilePlacementResult TryPlaceTile(DoorwayPair pair, DungeonArchetype archetype, out TileProxy tile) { tile = null; TileProxy nextTemplate = pair.NextTemplate; DoorwayProxy previousDoorway = pair.PreviousDoorway; if (nextTemplate == null) { return TilePlacementResult.TemplateIsNull; } int index = pair.NextTemplate.Doorways.IndexOf(pair.NextDoorway); tile = new TileProxy(nextTemplate); tile.Placement.IsOnMainPath = Status == GenerationStatus.MainPath; tile.Placement.Archetype = archetype; tile.Placement.TileSet = pair.NextTileSet; if (previousDoorway != null) { DoorwayProxy myDoorway = tile.Doorways[index]; tile.PositionBySocket(myDoorway, previousDoorway); Bounds bounds = tile.Placement.Bounds; if (RestrictDungeonToBounds && !TilePlacementBounds.Contains(bounds)) { return TilePlacementResult.OutOfBounds; } if (IsCollidingWithAnyTile(tile, previousDoorway.TileProxy)) { return TilePlacementResult.TileIsColliding; } } if (tile == null) { return TilePlacementResult.NewTileIsNull; } if (tile.Placement.IsOnMainPath) { if (pair.PreviousTile != null) { tile.Placement.PathDepth = pair.PreviousTile.Placement.PathDepth + 1; } } else { tile.Placement.PathDepth = pair.PreviousTile.Placement.PathDepth; tile.Placement.BranchDepth = ((!pair.PreviousTile.Placement.IsOnMainPath) ? (pair.PreviousTile.Placement.BranchDepth + 1) : 0); } if (previousDoorway != null) { DoorwayProxy b = tile.Doorways[index]; proxyDungeon.MakeConnection(previousDoorway, b); } proxyDungeon.AddTile(tile); return TilePlacementResult.None; } protected TileProxy GetTileTemplate(GameObject prefab) { TileProxy tileProxy = preProcessData.Where((TileProxy x) => x.Prefab == prefab).FirstOrDefault(); if (tileProxy == null) { tileProxy = new TileProxy(prefab, IgnoreSpriteBounds, UpVector); preProcessData.Add(tileProxy); } return tileProxy; } protected TileProxy PickRandomTemplate(DoorwaySocket socketGroupFilter) { GameObject prefab = useableTiles[RandomStream.Next(0, useableTiles.Count)]; TileProxy tileTemplate = GetTileTemplate(prefab); if (socketGroupFilter != null && !tileTemplate.UnusedDoorways.Where((DoorwayProxy d) => d.Socket == socketGroupFilter).Any()) { return PickRandomTemplate(socketGroupFilter); } return tileTemplate; } protected int NormalizedDepthToIndex(float normalizedDepth) { return Mathf.RoundToInt(normalizedDepth * (float)(targetLength - 1)); } protected float IndexToNormalizedDepth(int index) { return (float)index / (float)targetLength; } protected bool IsCollidingWithAnyTile(TileProxy newTile, TileProxy previousTile) { foreach (TileProxy allTile in proxyDungeon.AllTiles) { bool flag = previousTile == allTile; float maxOverlap = (flag ? OverlapThreshold : (0f - Padding)); if (DisallowOverhangs && !flag) { if (newTile.IsOverlappingOrOverhanging(allTile, UpDirection, maxOverlap)) { return true; } } else if (newTile.IsOverlapping(allTile, maxOverlap)) { return true; } } return false; } public void RegisterPostProcessStep(Action postProcessCallback, int priority = 0, PostProcessPhase phase = PostProcessPhase.AfterBuiltIn) { postProcessSteps.Add(new DungeonGeneratorPostProcessStep(postProcessCallback, priority, phase)); } public void UnregisterPostProcessStep(Action postProcessCallback) { for (int i = 0; i < postProcessSteps.Count; i++) { if (postProcessSteps[i].PostProcessCallback == postProcessCallback) { postProcessSteps.RemoveAt(i); } } } protected virtual IEnumerator PostProcess() { int length = proxyDungeon.MainPathTiles.Count; int maxBranchDepth = 0; if (proxyDungeon.BranchPathTiles.Count > 0) { List list = proxyDungeon.BranchPathTiles.ToList(); list.Sort((TileProxy a, TileProxy b) => b.Placement.BranchDepth.CompareTo(a.Placement.BranchDepth)); maxBranchDepth = list[0].Placement.BranchDepth; } yield return null; GenerationStats.BeginTime(GenerationStatus.PostProcessing); ChangeStatus(GenerationStatus.PostProcessing); postProcessSteps.Sort((DungeonGeneratorPostProcessStep a, DungeonGeneratorPostProcessStep b) => b.Priority.CompareTo(a.Priority)); foreach (DungeonGeneratorPostProcessStep step2 in postProcessSteps) { if (ShouldSkipFrame(isRoomPlacement: false)) { yield return null; } if (step2.Phase == PostProcessPhase.BeforeBuiltIn) { step2.PostProcessCallback(this); } } yield return null; if (!IsAnalysis) { foreach (Tile tile2 in CurrentDungeon.AllTiles) { if (ShouldSkipFrame(isRoomPlacement: false)) { yield return null; } tile2.Placement.NormalizedPathDepth = (float)tile2.Placement.PathDepth / (float)(length - 1); } CurrentDungeon.PostGenerateDungeon(this); foreach (Tile tile2 in CurrentDungeon.AllTiles) { if (ShouldSkipFrame(isRoomPlacement: false)) { yield return null; } ProcessProps(tile2, tile2.gameObject); } ProcessGlobalProps(); if (DungeonFlow.KeyManager != null) { PlaceLocksAndKeys(); } } GenerationStats.SetRoomStatistics(CurrentDungeon.MainPathTiles.Count, CurrentDungeon.BranchPathTiles.Count, maxBranchDepth); preProcessData.Clear(); yield return null; foreach (DungeonGeneratorPostProcessStep step2 in postProcessSteps) { if (ShouldSkipFrame(isRoomPlacement: false)) { yield return null; } if (step2.Phase == PostProcessPhase.AfterBuiltIn) { step2.PostProcessCallback(this); } } GenerationStats.EndTime(); foreach (GameObject door in CurrentDungeon.Doors) { if (door != null) { door.SetActive(value: true); } } } protected void ProcessProps(Tile tile, GameObject root) { if (root == null) { return; } RandomProp[] components = root.GetComponents(); for (int i = 0; i < components.Length; i++) { components[i].Process(RandomStream, tile); } if (root == null) { return; } int childCount = root.transform.childCount; List list = new List(childCount); for (int j = 0; j < childCount; j++) { GameObject gameObject = root.transform.GetChild(j).gameObject; list.Add(gameObject); } foreach (GameObject item in list) { if (item != null) { ProcessProps(tile, item); } } } protected virtual void ProcessGlobalProps() { Dictionary dictionary = new Dictionary(); foreach (Tile allTile in CurrentDungeon.AllTiles) { GlobalProp[] componentsInChildren = allTile.GetComponentsInChildren(); foreach (GlobalProp globalProp in componentsInChildren) { GameObjectChanceTable value = null; if (!dictionary.TryGetValue(globalProp.PropGroupID, out value)) { value = new GameObjectChanceTable(); dictionary[globalProp.PropGroupID] = value; } float num = (allTile.Placement.IsOnMainPath ? globalProp.MainPathWeight : globalProp.BranchPathWeight); num *= globalProp.DepthWeightScale.Evaluate(allTile.Placement.NormalizedDepth); value.Weights.Add(new GameObjectChance(globalProp.gameObject, num, 0f, null)); } } foreach (GameObjectChanceTable value2 in dictionary.Values) { foreach (GameObjectChance weight in value2.Weights) { weight.Value.SetActive(value: false); } } List list = new List(dictionary.Count); foreach (KeyValuePair pair in dictionary) { if (list.Contains(pair.Key)) { UnityEngine.Debug.LogWarning("Dungeon Flow contains multiple entries for the global prop group ID: " + pair.Key + ". Only the first entry will be used."); continue; } DungeonFlow.GlobalPropSettings globalPropSettings = DungeonFlow.GlobalProps.Where((DungeonFlow.GlobalPropSettings x) => x.ID == pair.Key).FirstOrDefault(); if (globalPropSettings == null) { continue; } GameObjectChanceTable gameObjectChanceTable = pair.Value.Clone(); int random = globalPropSettings.Count.GetRandom(RandomStream); random = Mathf.Clamp(random, 0, gameObjectChanceTable.Weights.Count); for (int j = 0; j < random; j++) { GameObjectChance random2 = gameObjectChanceTable.GetRandom(RandomStream, isOnMainPath: true, 0f, null, allowImmediateRepeats: true, removeFromTable: true); if (random2 != null && random2.Value != null) { random2.Value.SetActive(value: true); } } list.Add(pair.Key); } } protected virtual void PlaceLocksAndKeys() { GraphNode[] array = (from x in CurrentDungeon.ConnectionGraph.Nodes select x.Tile.Placement.GraphNode into x where x != null select x).Distinct().ToArray(); GraphLine[] array2 = (from x in CurrentDungeon.ConnectionGraph.Nodes select x.Tile.Placement.GraphLine into x where x != null select x).Distinct().ToArray(); Dictionary lockedDoorways = new Dictionary(); GraphNode[] array3 = array; foreach (GraphNode node in array3) { foreach (KeyLockPlacement @lock in node.Locks) { Tile tile = CurrentDungeon.AllTiles.Where((Tile x) => x.Placement.GraphNode == node).FirstOrDefault(); List connections = CurrentDungeon.ConnectionGraph.Nodes.Where((DungeonGraphNode x) => x.Tile == tile).FirstOrDefault().Connections; Doorway doorway = null; Doorway doorway2 = null; foreach (DungeonGraphConnection item in connections) { if (item.DoorwayA.Tile == tile) { doorway2 = item.DoorwayA; } else if (item.DoorwayB.Tile == tile) { doorway = item.DoorwayB; } } Key keyByID = node.Graph.KeyManager.GetKeyByID(@lock.ID); if (doorway != null && (node.LockPlacement & NodeLockPlacement.Entrance) == NodeLockPlacement.Entrance) { lockedDoorways.Add(doorway, keyByID); } if (doorway2 != null && (node.LockPlacement & NodeLockPlacement.Exit) == NodeLockPlacement.Exit) { lockedDoorways.Add(doorway2, keyByID); } } } GraphLine[] array4 = array2; foreach (GraphLine line in array4) { List list = (from x in CurrentDungeon.ConnectionGraph.Connections.Where(delegate(DungeonGraphConnection x) { bool flag4 = lockedDoorways.ContainsKey(x.DoorwayA) || lockedDoorways.ContainsKey(x.DoorwayB); bool flag5 = x.DoorwayA.Tile.Placement.TileSet.LockPrefabs.Count > 0; return x.DoorwayA.Tile.Placement.GraphLine == line && x.DoorwayB.Tile.Placement.GraphLine == line && !flag4 && flag5; }) select x.DoorwayA).ToList(); if (list.Count == 0) { continue; } foreach (KeyLockPlacement lock2 in line.Locks) { int random = lock2.Range.GetRandom(RandomStream); random = Mathf.Clamp(random, 0, list.Count); for (int j = 0; j < random; j++) { if (list.Count == 0) { break; } Doorway doorway3 = list[RandomStream.Next(0, list.Count)]; list.Remove(doorway3); if (!lockedDoorways.ContainsKey(doorway3)) { Key keyByID2 = line.Graph.KeyManager.GetKeyByID(lock2.ID); lockedDoorways.Add(doorway3, keyByID2); } } } } foreach (Tile allTile in CurrentDungeon.AllTiles) { if (allTile.Placement.InjectionData == null || !allTile.Placement.InjectionData.IsLocked) { continue; } List list2 = new List(); foreach (Doorway usedDoorway in allTile.UsedDoorways) { bool num = lockedDoorways.ContainsKey(usedDoorway) || lockedDoorways.ContainsKey(usedDoorway.ConnectedDoorway); bool flag = allTile.Placement.TileSet.LockPrefabs.Count > 0; bool flag2 = allTile.GetEntranceDoorway() == usedDoorway; if (!num && flag && flag2) { list2.Add(usedDoorway); } } if (list2.Any()) { Doorway key2 = list2.First(); Key keyByID3 = DungeonFlow.KeyManager.GetKeyByID(allTile.Placement.InjectionData.LockID); lockedDoorways.Add(key2, keyByID3); } } List list3 = new List(); foreach (KeyValuePair item2 in lockedDoorways) { Doorway key3 = item2.Key; Key key = item2.Value; List list4 = new List(); foreach (Tile allTile2 in CurrentDungeon.AllTiles) { if (!(allTile2.Placement.NormalizedPathDepth >= key3.Tile.Placement.NormalizedPathDepth)) { bool flag3 = false; if (allTile2.Placement.GraphNode != null && allTile2.Placement.GraphNode.Keys.Where((KeyLockPlacement x) => x.ID == key.ID).Count() > 0) { flag3 = true; } else if (allTile2.Placement.GraphLine != null && allTile2.Placement.GraphLine.Keys.Where((KeyLockPlacement x) => x.ID == key.ID).Count() > 0) { flag3 = true; } if (flag3) { list4.Add(allTile2); } } } List list5 = list4.SelectMany((Tile x) => x.GetComponentsInChildren().OfType()).ToList(); if (list5.Count == 0) { list3.Add(key3); continue; } int random2 = key.KeysPerLock.GetRandom(RandomStream); random2 = Math.Min(random2, list5.Count); for (int k = 0; k < random2; k++) { int index = RandomStream.Next(0, list5.Count); IKeySpawnable keySpawnable = list5[index]; keySpawnable.SpawnKey(key, DungeonFlow.KeyManager); foreach (IKeyLock item3 in (keySpawnable as Component).GetComponentsInChildren().OfType()) { item3.OnKeyAssigned(key, DungeonFlow.KeyManager); } list5.RemoveAt(index); } } foreach (Doorway item4 in list3) { lockedDoorways.Remove(item4); } foreach (KeyValuePair item5 in lockedDoorways) { item5.Key.RemoveUsedPrefab(); LockDoorway(item5.Key, item5.Value, DungeonFlow.KeyManager); } } protected virtual void LockDoorway(Doorway doorway, Key key, KeyManager keyManager) { TilePlacementData placement = doorway.Tile.Placement; GameObjectChanceTable[] array = (from x in doorway.Tile.Placement.TileSet.LockPrefabs.Where(delegate(LockedDoorwayAssociation x) { DoorwaySocket socket = x.Socket; return socket == null || DoorwaySocket.CanSocketsConnect(socket, doorway.Socket); }) select x.LockPrefabs).ToArray(); if (array.Length == 0) { return; } GameObject gameObject = UnityEngine.Object.Instantiate(array[RandomStream.Next(0, array.Length)].GetRandom(RandomStream, placement.IsOnMainPath, placement.NormalizedDepth, null, allowImmediateRepeats: true).Value, doorway.transform); DungeonUtil.AddAndSetupDoorComponent(CurrentDungeon, gameObject, doorway); doorway.SetUsedPrefab(gameObject); doorway.ConnectedDoorway.SetUsedPrefab(gameObject); foreach (IKeyLock item in gameObject.GetComponentsInChildren().OfType()) { item.OnKeyAssigned(key, keyManager); } } public void OnBeforeSerialize() { fileVersion = 1; } public void OnAfterDeserialize() { if (fileVersion < 1) { RepeatMode = ((!allowImmediateRepeats) ? TileRepeatMode.DisallowImmediate : TileRepeatMode.Allow); } } } }