protected virtual void LockDoorway(Doorway doorway, Key key, KeyManager keyManager) { var placement = doorway.Tile.Placement; var prefabs = doorway.Tile.TileSet.LockPrefabs.Where(x => { return(DoorwaySocket.IsMatchingSocket(x.SocketGroup, doorway.SocketGroup)); }).Select(x => x.LockPrefabs).ToArray(); if (prefabs.Length == 0) { return; } var chosenEntry = prefabs[RandomStream.Next(0, prefabs.Length)].GetRandom(RandomStream, placement.IsOnMainPath, placement.NormalizedDepth, null, true); var prefab = chosenEntry.Value; GameObject doorObj = GameObject.Instantiate(prefab); doorObj.transform.parent = Root.transform; doorObj.transform.position = doorway.transform.position; doorObj.transform.rotation = doorway.transform.rotation; // Set this locked door as the current door prefab doorway.SetUsedPrefab(doorObj); doorway.ConnectedDoorway.SetUsedPrefab(doorObj); DungeonUtil.AddAndSetupDoorComponent(CurrentDungeon, doorObj, doorway); foreach (var keylock in doorObj.GetComponentsInChildren <Component>().OfType <IKeyLock>()) { keylock.OnKeyAssigned(key, keyManager); } }
protected PreProcessTileData PickRandomTemplate(DoorwaySocketType?socketGroupFilter) { // Pick a random tile var tile = useableTiles[RandomStream.Next(0, useableTiles.Count)]; var template = GetTileTemplate(tile); // If there's a socket group filter and the chosen Tile doesn't have a socket of this type, try again if (socketGroupFilter.HasValue && !template.DoorwaySockets.Contains(socketGroupFilter.Value)) { return(PickRandomTemplate(socketGroupFilter)); } return(template); }
protected virtual IEnumerator GenerateMainPath() { ChangeStatus(GenerationStatus.MainPath); nextNodeIndex = 0; List <GraphNode> handledNodes = new List <GraphNode>(DungeonFlow.Nodes.Count); bool isDone = false; int i = 0; // Keep track of these now, we'll need them later when we know the actual length of the dungeon List <List <TileSet> > tileSets = new List <List <TileSet> >(targetLength); List <DungeonArchetype> archetypes = new List <DungeonArchetype>(targetLength); List <GraphNode> nodes = new List <GraphNode>(targetLength); List <GraphLine> lines = new List <GraphLine>(targetLength); // We can't rigidly stick to the target length since we need at least one room for each node and that might be more than targetLength while (!isDone) { float depth = Mathf.Clamp(i / (float)(targetLength - 1), 0, 1); GraphLine lineSegment = DungeonFlow.GetLineAtDepth(depth); // This should never happen if (lineSegment == null) { yield return(Wait(InnerGenerate(true))); yield break; } // We're on a new line segment, change the current archetype if (lineSegment != previousLineSegment) { currentArchetype = lineSegment.DungeonArchetypes[RandomStream.Next(0, lineSegment.DungeonArchetypes.Count)]; previousLineSegment = lineSegment; } List <TileSet> useableTileSets = null; GraphNode nextNode = null; var orderedNodes = DungeonFlow.Nodes.OrderBy(x => x.Position).ToArray(); // Determine which node comes next foreach (var node in orderedNodes) { if (depth >= node.Position && !handledNodes.Contains(node)) { nextNode = node; handledNodes.Add(node); break; } } // Assign the TileSets to use based on whether we're on a node or a line segment if (nextNode != null) { useableTileSets = nextNode.TileSets; nextNodeIndex = (nextNodeIndex >= orderedNodes.Length - 1) ? -1 : nextNodeIndex + 1; archetypes.Add(null); lines.Add(null); nodes.Add(nextNode); if (nextNode == orderedNodes[orderedNodes.Length - 1]) { isDone = true; } } else { useableTileSets = currentArchetype.TileSets; archetypes.Add(currentArchetype); lines.Add(lineSegment); nodes.Add(null); } tileSets.Add(useableTileSets); i++; } int tileRetryCount = 0; int totalForLoopRetryCount = 0; for (int j = 0; j < tileSets.Count; j++) { var attachTo = (j == 0) ? null : currentDungeon.MainPathTiles[currentDungeon.MainPathTiles.Count - 1]; var tile = AddTile(attachTo, tileSets[j], j / (float)(tileSets.Count - 1), archetypes[j]); // if no tile could be generated delete last successful tile and retry from previous index // else return false if (j > 5 && tile == null && tileRetryCount < 5 && totalForLoopRetryCount < 20) { Tile previousTile = currentDungeon.MainPathTiles[j - 1]; foreach (var doorway in previousTile.Placement.AllDoorways) { allDoorways.Remove(doorway); } // If the tile we're removing was placed by tile injection, be sure to place the injected tile back on the pending list InjectedTile previousInjectedTile; if (injectedTiles.TryGetValue(previousTile, out previousInjectedTile)) { tilesPendingInjection.Add(previousInjectedTile); injectedTiles.Remove(previousTile); } currentDungeon.RemoveLastConnection(); currentDungeon.RemoveTile(previousTile); UnityUtil.Destroy(previousTile.gameObject); j -= 2; // -2 because loop adds 1 tileRetryCount++; totalForLoopRetryCount++; } else if (tile == null) { yield return(Wait(InnerGenerate(true))); yield break; } else { tile.Node = nodes[j]; tile.Line = lines[j]; tileRetryCount = 0; // Wait for a frame to allow for animated loading screens, etc if (ShouldSkipFrame(true)) { yield return(GetRoomPause()); } } } yield break; // Required for generation to run synchronously }
protected virtual IEnumerator InnerGenerate(bool isRetry) { if (isRetry) { ChosenSeed = RandomStream.Next(); RandomStream = new Random(ChosenSeed); if (retryCount >= MaxAttemptCount && Application.isEditor) { string errorText = "Failed to generate the dungeon " + MaxAttemptCount + " times.\n" + "This 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.\n" + "Here are a list of all reasons a tile placement had to be retried:"; foreach (var pair in tilePlacementResultCounters) { if (pair.Value > 0) { errorText += "\n" + pair.Key + " (x" + pair.Value + ")"; } } Debug.LogError(errorText); ChangeStatus(GenerationStatus.Failed); yield break; } retryCount++; GenerationStats.IncrementRetryCount(); if (Retrying != null) { Retrying(); } } else { retryCount = 0; GenerationStats.Clear(); } currentDungeon = Root.GetComponent <Dungeon>(); if (currentDungeon == null) { currentDungeon = Root.AddComponent <Dungeon>(); } currentDungeon.DebugRender = DebugRender; currentDungeon.PreGenerateDungeon(this); Clear(false); targetLength = Mathf.RoundToInt(DungeonFlow.Length.GetRandom(RandomStream) * LengthMultiplier); targetLength = Mathf.Max(targetLength, 2); // Tile Injection GenerationStats.BeginTime(GenerationStatus.TileInjection); if (tilesPendingInjection == null) { tilesPendingInjection = new List <InjectedTile>(); } else { tilesPendingInjection.Clear(); } injectedTiles.Clear(); GatherTilesToInject(); // Pre-Processing GenerationStats.BeginTime(GenerationStatus.PreProcessing); PreProcess(); // Main Path Generation GenerationStats.BeginTime(GenerationStatus.MainPath); yield return(Wait(GenerateMainPath())); // We may have had to retry when generating the main path, if so, the status will be either Complete or Failed and we should exit here if (Status == GenerationStatus.Complete || Status == GenerationStatus.Failed) { yield break; } // Branch Paths Generation GenerationStats.BeginTime(GenerationStatus.Branching); yield return(Wait(GenerateBranchPaths())); // If there are any required tiles missing from the tile injection stage, the generation process should fail foreach (var tileInjection in tilesPendingInjection) { if (tileInjection.IsRequired) { yield return(Wait(InnerGenerate(true))); yield break; } } // We may have missed some required injected tiles and have had to retry, if so, the status will be either Complete or Failed and we should exit here if (Status == GenerationStatus.Complete || Status == GenerationStatus.Failed) { yield break; } // Post-Processing yield return(Wait(PostProcess())); // Waiting one frame so objects are in their expected state yield return(null); ChangeStatus(GenerationStatus.Complete); // Let DungenCharacters know that they should re-check the Tile they're in foreach (var character in Component.FindObjectsOfType <DungenCharacter>()) { character.ForceRecheckTile(); } }
protected virtual void PlaceLocksAndKeys() { var nodes = currentDungeon.ConnectionGraph.Nodes.Select(x => x.Tile.Node).Where(x => { return(x != null); }).Distinct().ToArray(); var lines = currentDungeon.ConnectionGraph.Nodes.Select(x => x.Tile.Line).Where(x => { return(x != null); }).Distinct().ToArray(); Dictionary <Doorway, Key> lockedDoorways = new Dictionary <Doorway, Key>(); // Lock doorways on nodes foreach (var node in nodes) { foreach (var l in node.Locks) { var tile = currentDungeon.AllTiles.Where(x => { return(x.Node == node); }).FirstOrDefault(); var connections = currentDungeon.ConnectionGraph.Nodes.Where(x => { return(x.Tile == tile); }).FirstOrDefault().Connections; Doorway entrance = null; Doorway exit = null; foreach (var conn in connections) { if (conn.DoorwayA.Tile == tile) { exit = conn.DoorwayA; } else if (conn.DoorwayB.Tile == tile) { entrance = conn.DoorwayB; } } var key = node.Graph.KeyManager.GetKeyByID(l.ID); if (key.Prefab != null) { if (entrance != null && (node.LockPlacement & NodeLockPlacement.Entrance) == NodeLockPlacement.Entrance) { lockedDoorways.Add(entrance, key); } if (exit != null && (node.LockPlacement & NodeLockPlacement.Exit) == NodeLockPlacement.Exit) { lockedDoorways.Add(exit, key); } } else { Debug.LogError("Key with ID " + l.ID + " does not have a prefab to place"); } } } // Lock doorways on lines foreach (var line in lines) { var doorways = currentDungeon.ConnectionGraph.Connections.Where(x => { bool isDoorwayAlreadyLocked = lockedDoorways.ContainsKey(x.DoorwayA) || lockedDoorways.ContainsKey(x.DoorwayB); bool doorwayHasLockPrefabs = x.DoorwayA.Tile.TileSet.LockPrefabs.Count > 0; return(x.DoorwayA.Tile.Line == line && x.DoorwayB.Tile.Line == line && !isDoorwayAlreadyLocked && doorwayHasLockPrefabs); }).Select(x => x.DoorwayA).ToList(); if (doorways.Count == 0) { continue; } foreach (var l in line.Locks) { int lockCount = l.Range.GetRandom(RandomStream); lockCount = Mathf.Clamp(lockCount, 0, doorways.Count); for (int i = 0; i < lockCount; i++) { if (doorways.Count == 0) { break; } var doorway = doorways[RandomStream.Next(0, doorways.Count)]; doorways.Remove(doorway); if (lockedDoorways.ContainsKey(doorway)) { continue; } var key = line.Graph.KeyManager.GetKeyByID(l.ID); lockedDoorways.Add(doorway, key); } } } List <Doorway> locksToRemove = new List <Doorway>(); foreach (var pair in lockedDoorways) { var door = pair.Key; var key = pair.Value; List <Tile> possibleSpawnTiles = new List <Tile>(); foreach (var t in currentDungeon.AllTiles) { if (t.Placement.NormalizedPathDepth >= door.Tile.Placement.NormalizedPathDepth) { continue; } bool canPlaceKey = false; if (t.Node != null && t.Node.Keys.Where(x => { return(x.ID == key.ID); }).Count() > 0) { canPlaceKey = true; } else if (t.Line != null && t.Line.Keys.Where(x => { return(x.ID == key.ID); }).Count() > 0) { canPlaceKey = true; } if (!canPlaceKey) { continue; } possibleSpawnTiles.Add(t); } var possibleSpawnComponents = possibleSpawnTiles.SelectMany(x => x.GetComponentsInChildren <Component>().OfType <IKeySpawnable>()).ToList(); if (possibleSpawnComponents.Count == 0) { locksToRemove.Add(door); } else { int keysToSpawn = key.KeysPerLock.GetRandom(RandomStream); keysToSpawn = Math.Min(keysToSpawn, possibleSpawnComponents.Count); for (int i = 0; i < keysToSpawn; i++) { int chosenCompID = RandomStream.Next(0, possibleSpawnComponents.Count); var comp = possibleSpawnComponents[chosenCompID]; comp.SpawnKey(key, DungeonFlow.KeyManager); foreach (var k in (comp as Component).GetComponentsInChildren <Component>().OfType <IKeyLock>()) { k.OnKeyAssigned(key, DungeonFlow.KeyManager); } possibleSpawnComponents.RemoveAt(chosenCompID); } } } foreach (var door in locksToRemove) { lockedDoorways.Remove(door); } foreach (var pair in lockedDoorways) { pair.Key.RemoveUsedPrefab(); LockDoorway(pair.Key, pair.Value, DungeonFlow.KeyManager); } }