using System.Collections; using System.Collections.Generic; using Sirenix.OdinInspector; using UnityEngine; namespace Hallucinate.GameSetup.Maze { /// /// Central controller for the Maze system. /// Manages algorithm selection, debug speed, and regeneration. /// public class MazeManager : MonoBehaviour { public enum AlgorithmType { Recursive, Wilsons, Prims, Crawler, NoiseRecursive } [BoxGroup("Generation")] [InfoBox("Set the array size to control how many maze floors are generated. Runtime grid data is rebuilt when Regenerate runs.")] [ValidateInput(nameof(HasAtLeastOneFloor), "Maze Manager needs at least one floor.")] public MazeGrid[] mazes; [BoxGroup("Generation")] [MinValue(0.001f)] public float floorHeight = 3.5f; [BoxGroup("Generation")] [MinValue(0)] public int connectionsPerFloor = 2; [BoxGroup("Generation")] [SerializeField] private AlgorithmType selectedAlgorithm; [BoxGroup("Generation/Grid Size")] [PropertyRange(5, 200)] [SerializeField] private int width = 30; [BoxGroup("Generation/Grid Size")] [PropertyRange(5, 200)] [SerializeField] private int depth = 30; [BoxGroup("Animation")] [SerializeField] private bool animateGeneration = true; [BoxGroup("Animation")] [ShowIf(nameof(animateGeneration))] [PropertyRange(1, 500)] [LabelText("Generation Speed (Cells/Frame)")] [SerializeField] private int generationSpeed = 50; public static int cellsProcessedThisFrame; [BoxGroup("Animation")] [ShowIf(nameof(animateGeneration))] public MazeRenderer.CellAnimationType cellAnimationType = MazeRenderer.CellAnimationType.ScaleUp; [BoxGroup("Progress")] [ProgressBar(0, 100)] [ShowInInspector, ReadOnly] private float completionPercentage; [BoxGroup("References")] [Required] [SerializeField] private MazeRenderer mazeRenderer; [BoxGroup("Rooms (Phase 2)")] public bool generateRooms = true; [BoxGroup("Rooms (Phase 2)")] [ShowIf(nameof(generateRooms))] public int numberOfRooms = 2; [BoxGroup("Rooms (Phase 2)")] [ShowIf(nameof(generateRooms))] public Vector2Int minRoomSize = new Vector2Int(2, 2); [BoxGroup("Rooms (Phase 2)")] [ShowIf(nameof(generateRooms))] public Vector2Int maxRoomSize = new Vector2Int(4, 4); [BoxGroup("References")] [Required] [SerializeField] private Transform mazeContainer; [FoldoutGroup("Manhole Prefabs")] public GameObject straightManHoleLadder; [FoldoutGroup("Manhole Prefabs")] public GameObject straightManHoleUp; [FoldoutGroup("Manhole Prefabs")] public GameObject deadendManHoleLadder; [FoldoutGroup("Manhole Prefabs")] public GameObject deadendManHoleUp; [ShowInInspector] [ReadOnly] [BoxGroup("Runtime")] private int FloorCount => mazes?.Length ?? 0; [ShowInInspector] [ReadOnly] [BoxGroup("Runtime")] private string CurrentGrid => _grid == null ? "None" : $"{_grid.Width}x{_grid.Depth}, Level {_grid.Level}"; private MazeGrid _grid; private Coroutine _generationCoroutine; private HashSet _modifiedCells = new HashSet(); private void Start() { Regenerate(); } private void Update() { if (Input.GetKeyDown(KeyCode.R)) { Regenerate(); } } [ContextMenu("Clear Maze")] [Button("Clear Maze", ButtonSizes.Large)] public void ClearMaze() { if (_generationCoroutine != null) { StopCoroutine(_generationCoroutine); _generationCoroutine = null; } if (mazeRenderer != null) { mazeRenderer.Clear(); } completionPercentage = 0f; _modifiedCells?.Clear(); } [ContextMenu("Regenerate")] [Button("Regenerate Maze", ButtonSizes.Large)] public void Regenerate() { if (mazeRenderer == null) { Debug.LogError("MazeManager needs a MazeRenderer reference before regenerating.", this); return; } if (mazeContainer == null) { Debug.LogError("MazeManager needs a maze container reference before regenerating.", this); return; } if (mazes == null || mazes.Length == 0) { mazes = new MazeGrid[1]; } ClearMaze(); mazeRenderer.currentAnimationType = cellAnimationType; if (animateGeneration) { _generationCoroutine = StartCoroutine(GenerateMazeRoutine()); } else { GenerateMazeInstant(); } } private void GenerateMazeInstant() { _modifiedCells.Clear(); completionPercentage = 0f; for (int i = 0; i < mazes.Length; i++) { mazes[i] = new MazeGrid(width, depth); mazes[i].Level = i; CarveRooms(mazes[i]); IMazeAlgorithm algorithmForFloor = GetAlgorithm(selectedAlgorithm); algorithmForFloor.Generate(mazes[i]); } GenerateConnections(); for (int i = 0; i < mazes.Length; i++) { mazeRenderer.Initialize(mazes[i], mazeContainer, i == 0); } if (mazes.Length > 0) _grid = mazes[0]; completionPercentage = 100f; } private IEnumerator GenerateMazeRoutine() { _modifiedCells.Clear(); completionPercentage = 0f; for (int i = 0; i < mazes.Length; i++) { mazes[i] = new MazeGrid(width, depth); mazes[i].Level = i; int floorIndex = i; mazes[i].OnCellChanged += (x, z, type) => { if (type != MazeCellType.Wall) { _modifiedCells.Add(new Vector3Int(x, floorIndex, z)); int totalCells = width * depth * mazes.Length; // Approximate the progress to reach roughly 100% since algorithms don't visit all cells float fillRatio = 0.6f; completionPercentage = Mathf.Clamp((_modifiedCells.Count / ((float)totalCells * fillRatio)) * 100f, 0, 99f); } }; CarveRooms(mazes[i]); mazeRenderer.Initialize(mazes[i], mazeContainer, i == 0); IMazeAlgorithm algorithmForFloor = GetAlgorithm(selectedAlgorithm); cellsProcessedThisFrame = 0; yield return StartCoroutine(algorithmForFloor.GenerateStepByStep(mazes[i], generationSpeed)); } GenerateConnections(); if (mazes.Length > 0) _grid = mazes[0]; completionPercentage = 100f; _generationCoroutine = null; } private void GenerateConnections() { // Step 2: Create connections between adjacent floors for (int i = 0; i < mazes.Length - 1; i++) { MazeGrid currentFloor = mazes[i]; MazeGrid nextFloor = mazes[i + 1]; List possibleConnections = new List(); for (int z = 0; z < depth; z++) { for (int x = 0; x < width; x++) { // Check if both floors have a corridor at this position bool isCurrentFloorPath = currentFloor.GetCell(x, z) == MazeCellType.Corridor; bool isNextFloorPath = nextFloor.GetCell(x, z) == MazeCellType.Corridor; if (isCurrentFloorPath && isNextFloorPath) { possibleConnections.Add(new Vector2Int(x, z)); } } } ShuffleList(possibleConnections); int connectionsMade = 0; foreach (Vector2Int pos in possibleConnections) { if (connectionsMade >= connectionsPerFloor) break; int x = pos.x; int z = pos.y; // Set stair cells currentFloor.SetCell(x, z, MazeCellType.StairsUp); nextFloor.SetCell(x, z, MazeCellType.StairsDown); connectionsMade++; } } } private void CarveRooms(MazeGrid grid) { if (!generateRooms) return; for (int i = 0; i < numberOfRooms; i++) { int w = Random.Range(minRoomSize.x, maxRoomSize.x + 1); int d = Random.Range(minRoomSize.y, maxRoomSize.y + 1); int startX = Random.Range(1, width - w - 1); int startZ = Random.Range(1, depth - d - 1); for (int x = startX; x < startX + w; x++) { for (int z = startZ; z < startZ + d; z++) { grid.SetCell(x, z, MazeCellType.Room); } } // Carve guaranteed door to seed pathfinding if (Random.value > 0.5f) { int doorX = Random.Range(startX, startX + w); int doorZ = Random.value > 0.5f ? startZ + d : startZ - 1; grid.SetCell(doorX, doorZ, MazeCellType.Corridor); } else { int doorX = Random.value > 0.5f ? startX + w : startX - 1; int doorZ = Random.Range(startZ, startZ + d); grid.SetCell(doorX, doorZ, MazeCellType.Corridor); } } } private void ShuffleList(List list) { for (int i = 0; i < list.Count; i++) { T temp = list[i]; int randomIndex = Random.Range(i, list.Count); list[i] = list[randomIndex]; list[randomIndex] = temp; } } private IMazeAlgorithm GetAlgorithm(AlgorithmType type) { return type switch { AlgorithmType.Recursive => new RecursiveAlgorithm(), AlgorithmType.Wilsons => new WilsonsAlgorithm(), AlgorithmType.Prims => new PrimsAlgorithm(), AlgorithmType.Crawler => new CrawlerAlgorithm(), AlgorithmType.NoiseRecursive => new NoiseRecursiveGenerator(), _ => new RecursiveAlgorithm() }; } [Button("Find Scene References")] private void FindSceneReferences() { if (mazeRenderer == null) { mazeRenderer = GetComponentInChildren(); } if (mazeContainer == null) { mazeContainer = transform; } } private bool HasAtLeastOneFloor(MazeGrid[] floors) { return floors != null && floors.Length > 0; } } }