using Godot; using System; using System.Collections.Generic; using System.Linq; using static WFC; public partial class Layer : Node3D { Random rand = new Random(); private Node3D decorationRoot; public Tile[,] tiles; int layerSize; Tile tile; int level; bool updateFailed = false; public bool hasContentGenerated = false; // Called when the node enters the scene tree for the first time. public override void _Ready() { decorationRoot = new Node3D { Name = "Decorations" }; AddChild(decorationRoot); } // Called every frame. 'delta' is the elapsed time since the previous frame. public override void _Process(double delta) { } public void ClearDecorations() { foreach (var tile in tiles) { foreach (Node child in tile.ContentNode.GetChildren()) { child.QueueFree(); } } } public void SetupLayer(int layerSize, int level, Dictionary tileMeshes) { this.layerSize = layerSize; this.level = level; tiles = new Tile[layerSize, layerSize]; GenerateBaseStructure(tileMeshes); int safetyCounter = 0; while (true) { if (GenerateLayer()) break; ResetLayer(tileMeshes); safetyCounter++; if (safetyCounter > 1000) break; } CreateTileNodes(); } private void GenerateBaseStructure(Dictionary tileMeshes) { Vector3 position; float offsetX; float offsetY = level * 4 * -1; float offsetZ; for (int x = 0; x < layerSize; x++) { offsetX = x * 6; for (int y = 0; y < layerSize; y++) { offsetZ = y * 6; position = new Vector3(offsetX, offsetY, offsetZ); tile = new Tile(); tile.SetMeshes(tileMeshes); tile.Position = position; tile.GridPosition = new Vector2I(x, y); tiles[x, y] = tile; } } } private void CreateTileNodes() { foreach (var tile in tiles) { var node = new Node3D { Position = tile.Position }; decorationRoot.AddChild(node); tile.ContentNode = node; } } private void ResetLayer(Dictionary tileMeshes) { for (int x = 0; x < layerSize; x++) { for (int y = 0; y < layerSize; y++) { tiles[x, y].Reset(tileMeshes); } } } private bool GenerateBorder() { for (int x = 0; x < layerSize; x++) { for (int y = 0; y < layerSize; y++) { if (x == 0 || y == 0 || x == layerSize - 1 || y == layerSize - 1) { var tile = tiles[x, y]; List possibilities = new(); if(x == 0 && y == 0) { possibilities.Add("corner_down_right"); } else if(x == 0 && y == layerSize - 1) { possibilities.Add("corner_up_right"); } else if(x == layerSize - 1 && y == 0) { possibilities.Add("corner_down_left"); } else if(x == layerSize - 1 && y == layerSize - 1) { possibilities.Add("corner_up_left"); } else if(y == 0) { possibilities.Add("straight_left_right"); possibilities.Add("t_down"); } else if(y == layerSize - 1) { possibilities.Add("straight_left_right"); possibilities.Add("t_up"); } else if(x == 0) { possibilities.Add("straight_up_down"); possibilities.Add("t_right"); } else if(x == layerSize - 1) { possibilities.Add("straight_up_down"); possibilities.Add("t_left"); } string result = tile.Collapse(possibilities[rand.Next(0, possibilities.Count)]); if (result == "ERR") return false; NewPropagate(new Vector2I(x, y)); } } } return true; } public bool GenerateLayer() { bool result = true; int safetyCounter = 0; if (!GenerateBorder()) { return false; } Vector2I position = GetSmallestPossibilities(); while (true) { string keyword = tiles[position.X, position.Y].Collapse(""); if (keyword == "ERR") return false; if (keyword != "") { NewPropagate(position); if (updateFailed) break; position = GetSmallestPossibilities(); if (position == new Vector2(-100, -100)) { break; } continue; } safetyCounter++; if (safetyCounter == layerSize * layerSize) return false; } if (updateFailed) return false; //Player has over 80% of tiles available without destroying walls => Results in about 95% success rate //Not necessarily needed but improves the overall generation percentage at a low resource cost if (!WFC.IsMapConnected(tiles, 0.8f)) return false; return result; } private void NewPropagate(Vector2I startPos) { Queue queue = new Queue(); queue.Enqueue(startPos); while (queue.Count > 0) { Vector2I currentPos = queue.Dequeue(); Tile currentTile = tiles[currentPos.X, currentPos.Y]; // Use CURRENT state of tile var currentPossibilities = currentTile.collapsedMesh != null ? new HashSet { currentTile.collapsedMesh } : new HashSet(currentTile.tileMeshes.Keys); for (int i = 0; i < dirs.Length; i++) { Vector2I newPos = currentPos + offsets[i]; if (!InBounds(newPos, layerSize)) continue; Tile neighborTile = tiles[newPos.X, newPos.Y]; HashSet allowed = new HashSet(); foreach (string neighborOption in neighborTile.tileMeshes.Keys) { foreach (string item in currentPossibilities) { if (WFC.CanConnect(item, neighborOption, dirs[i], false)) { allowed.Add(neighborOption); break; } } } int updateCount = neighborTile.Propagate(allowed); if (updateCount == int.MaxValue) { updateFailed = true; return; } // ONLY enqueue if something changed if (updateCount > 0) { queue.Enqueue(newPos); } } } } private Vector2I GetSmallestPossibilities() { Vector2I result = new Vector2I(-100, -100); int lowest = 100; int current; for (int x = 0; x < layerSize; x++) { for (int y = 0; y < layerSize; y++) { if (tiles[x, y].collapsedMesh != null) continue; current = tiles[x, y].tileMeshes.Count; if (current < lowest) { result = new Vector2I(x, y); lowest = current; } } } return result; } }