using Godot; using System.Collections.Generic; using static WFC; public partial class Layer : Node3D { private const int MaxGenerationAttempts = 1000; private static readonly Vector2I NoPosition = new Vector2I(-100, -100); private Node3D decorationRoot; public Tile[,] tiles; private int layerSize; public int level; private bool updateFailed = false; public bool hasContentGenerated = false; public Vector2I gateCoordinate; public List currentResources; public bool isGateOpen = false; public List gateIngredients = new(); public override void _Ready() { currentResources = new List(); decorationRoot = new Node3D { Name = "Decorations" }; AddChild(decorationRoot); } public void ClearDecorations() { foreach (Tile tile in tiles) { foreach (Node child in tile.ContentNode.GetChildren()) { child.QueueFree(); } } } public void OpenGate() { isGateOpen = true; Tile gateTile = tiles[gateCoordinate.X, gateCoordinate.Y]; if (gateTile.ContentNode != null) { gateTile.ContentNode.Visible = false; } } public void SetupLayer(int layerSize, int level, Dictionary tileMeshes, Vector2I collapseOrigin) { this.layerSize = layerSize; this.level = level; tiles = new Tile[layerSize, layerSize]; GenerateBaseStructure(tileMeshes); int safetyCounter = 0; while (true) { if (GenerateLayer(collapseOrigin)) break; ResetLayer(tileMeshes); safetyCounter++; if (safetyCounter > MaxGenerationAttempts) break; } CreateTileNodes(); } private void GenerateBaseStructure(Dictionary tileMeshes) { Vector3 position; float offsetX; float offsetY = level * GameData.tileHeight * -1; float offsetZ; for (int x = 0; x < layerSize; x++) { offsetX = x * GameData.tileWidth; for (int y = 0; y < layerSize; y++) { offsetZ = y * GameData.tileWidth; position = new Vector3(offsetX, offsetY, offsetZ); Tile tile = new Tile(); tile.SetMeshes(tileMeshes); tile.Position = position; tile.GridPosition = new Vector2I(x, y); tiles[x, y] = tile; } } } private void CreateTileNodes() { foreach (Tile tile in tiles) { Node3D node = new Node3D { Position = tile.Position, Visible = tile.collapsedMesh != null && tile.collapsedMesh == "gate" }; decorationRoot.AddChild(node); tile.ContentNode = node; } } private void ResetLayer(Dictionary tileMeshes) { updateFailed = false; for (int x = 0; x < layerSize; x++) { for (int y = 0; y < layerSize; y++) { tiles[x, y].Reset(tileMeshes); } } } private void GenerateBorder() { for (int x = 0; x < layerSize; x++) { for (int z = 0; z < layerSize; z++) { if (x == 0 && z == 0 && level == 0) continue; if (!IsBorder(x, z)) continue; Tile tile = tiles[x, z]; List possibilities = GetBorderPossibilities(x, z); if (possibilities.Count == 0) continue; tile.Collapse(possibilities[GameData.rand.Next(possibilities.Count)]); Propagate(new Vector2I(x, z)); } } } private bool IsBorder(int x, int z) { return x == 0 || z == 0 || x == layerSize - 1 || z == layerSize - 1; } private void GenerateNecessaryTiles() { //Generate spawn only in the first layer if (level == 0) { tiles[0, 0].Collapse("spawn"); Propagate(new Vector2I()); } int posX, posY; while (true) { posX = GameData.rand.Next(layerSize); posY = GameData.rand.Next(layerSize); if (tiles[posX, posY].collapsedMesh != null) continue; if (tiles[posX, posY].tileMeshes.ContainsKey("gate")) { tiles[posX, posY].Collapse("gate"); tiles[posX, posY].containsDecoration = true; gateCoordinate = new Vector2I(posX, posY); Propagate(gateCoordinate); break; } } } public bool GenerateLayer(Vector2I collapseOrigin) { int safetyCounter = 0; GenerateBorder(); GenerateNecessaryTiles(); Vector2I position = tiles[collapseOrigin.X, collapseOrigin.Y].collapsedMesh == null ? collapseOrigin : GetSmallestPossibilities(); while (true) { string keyword = tiles[position.X, position.Y].Collapse(""); if (keyword == "ERR") return false; if (keyword != "") { Propagate(position); if (updateFailed) break; position = GetSmallestPossibilities(); if (position == NoPosition) { break; } continue; } safetyCounter++; if (safetyCounter == layerSize * layerSize) return false; } if (updateFailed) return false; if (!WFC.IsMapConnected(tiles, 0.8f)) return false; return true; } private void Propagate(Vector2I startPos) { Queue queue = new Queue(); queue.Enqueue(startPos); while (queue.Count > 0) { Vector2I currentPos = queue.Dequeue(); Tile currentTile = tiles[currentPos.X, currentPos.Y]; HashSet currentPossibilities = currentTile.collapsedMesh != null ? new HashSet { currentTile.collapsedMesh } : new HashSet(currentTile.tileMeshes.Keys); for (int i = 0; i < offsets2D.Length; i++) { Vector2I newPos = currentPos + offsets2D[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; } if (updateCount > 0) { queue.Enqueue(newPos); } } } } private Vector2I GetSmallestPossibilities() { Vector2I result = NoPosition; 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; } public string DisplayGateIngredients() { string result = ""; foreach (Ingredient ingredient in gateIngredients) { result += $"{ItemData.GetReadableName(ingredient.Item)} ({ingredient.Amount})\r"; } return result; } }