using System; using System.Collections.Generic; using System.Linq; using Godot; public class WFC { public static Dictionary>> adjacency = new Dictionary>>(); public enum Direction { Backward, Forward, Left, Right, None, Up, Down } public static readonly Direction[] dirs = { Direction.Backward, Direction.Forward, Direction.Left, Direction.Right, Direction.Up, Direction.Down }; public static readonly Vector2I[] offsets2D = { new Vector2I(0, -1), new Vector2I(0, 1), new Vector2I(-1, 0), new Vector2I(1, 0) }; public static readonly Vector3I[] offsets3D = { new Vector3I(0, 0, -1), new Vector3I(0, 0, 1), new Vector3I(-1, 0, 0), new Vector3I(1, 0, 0), new Vector3I(0, -1, 0), new Vector3I(0, 1, 0) }; public static Dictionary> tileConnections = new Dictionary> { ["t_right"] = new() { Direction.Backward, Direction.Forward, Direction.Right, Direction.Up }, ["t_left"] = new() { Direction.Backward, Direction.Forward, Direction.Left, Direction.Up }, ["t_up"] = new() { Direction.Left, Direction.Right, Direction.Backward, Direction.Up }, ["t_down"] = new() { Direction.Left, Direction.Right, Direction.Forward, Direction.Up }, ["end_up"] = new() { Direction.Backward, Direction.Up }, ["end_down"] = new() { Direction.Forward, Direction.Up }, ["end_left"] = new() { Direction.Left, Direction.Up }, ["end_right"] = new() { Direction.Right, Direction.Up }, ["straight_left_right"] = new() { Direction.Left, Direction.Right, Direction.Up }, ["straight_up_down"] = new() { Direction.Backward, Direction.Forward, Direction.Up }, ["corner_up_left"] = new() { Direction.Backward, Direction.Left, Direction.Up }, ["corner_up_right"] = new() { Direction.Backward, Direction.Right, Direction.Up }, ["corner_down_left"] = new() { Direction.Forward, Direction.Left, Direction.Up }, ["corner_down_right"] = new() { Direction.Forward, Direction.Right, Direction.Up }, ["junction"] = new() { Direction.Backward, Direction.Forward, Direction.Left, Direction.Right, Direction.Up }, ["gate"] = new() { Direction.Backward, Direction.Forward, Direction.Left, Direction.Right, Direction.Up, Direction.Down } }; public static Dictionary weights = new() { ["junction"] = 3f, ["t_up"] = 3f, ["t_down"] = 3f, ["t_left"] = 3f, ["t_right"] = 3f, ["straight_left_right"] = 2f, ["straight_up_down"] = 2f, ["corner_up_left"] = 0.7f, ["corner_up_right"] = 0.7f, ["corner_down_left"] = 0.7f, ["corner_down_right"] = 0.7f, ["end_up"] = 0.2f, ["end_down"] = 0.1f, ["end_left"] = 0.2f, ["end_right"] = 0.3f, ["gate"] = 0.0f }; public static Direction Opposite(Direction dir) { return dir switch { Direction.Backward => Direction.Forward, Direction.Forward => Direction.Backward, Direction.Left => Direction.Right, Direction.Right => Direction.Left, Direction.Up => Direction.Down, Direction.Down => Direction.Up, _ => dir }; } public static bool CanConnect(string a, string b, Direction direction, bool checkWalking) { var aDirs = tileConnections[a]; var bDirs = tileConnections[b]; bool aOpen = aDirs.Contains(direction); bool bOpen = bDirs.Contains(Opposite(direction)); if (checkWalking) return aOpen && bOpen; return aOpen == bOpen; } public static void FillAdjacencies() { foreach (var tile in tileConnections.Keys) { adjacency[tile] = new Dictionary>(); foreach (Direction dir in Enum.GetValues(typeof(Direction))) { var valid = new List(); foreach (var other in tileConnections.Keys) { if (CanConnect(tile, other, dir, false)) valid.Add(other); } adjacency[tile][dir] = valid; } } } public static bool IsWalkable(Tile tile) { var dirs = tileConnections[tile.collapsedMesh]; return dirs.Count > 0 && !dirs.Contains(Direction.None); } public static bool CanWalk(Tile[,] layer, Vector2I from, Vector2I to, Direction dir) { var fromTile = layer[from.X, from.Y]; var toTile = layer[to.X, to.Y]; if (!IsWalkable(toTile)) return false; return CanConnect(fromTile.collapsedMesh, toTile.collapsedMesh, dir, true); } public static bool CanWalk3D(Vector3I from, Vector3I to) { Tile fromTile = GameData.map[from.Y].tiles[from.X, from.Z]; Tile toTile = GameData.map[to.Y].tiles[to.X, to.Z]; if (fromTile == null || toTile == null) return false; if (from.Y != to.Y) { if (Math.Abs(from.Y - to.Y) != 1) return false; if (from.Y > to.Y) { return toTile.collapsedMesh == "gate"; } else { return fromTile.collapsedMesh == "gate"; } } int dx = to.X - from.X; int dz = to.Z - from.Z; if (Math.Abs(dx) + Math.Abs(dz) != 1) return false; Direction dir; if (dx == 1) dir = Direction.Right; else if (dx == -1) dir = Direction.Left; else if (dz == 1) dir = Direction.Forward; else if (dz == -1) dir = Direction.Backward; else return false; return CanWalk( GameData.map[from.Y].tiles, new Vector2I(from.X, from.Z), new Vector2I(to.X, to.Z), dir ); } public static bool IsMapConnected(Tile[,] layer, float accessibilityThreshhold) { bool result = false; HashSet visited = new HashSet(); List toCheck = new List(); Vector2I position; toCheck.Add(new Vector2I(1, 1)); int safetyCounter = 0; while (true) { if (toCheck.Count <= 0) break; int index = GameData.rand.Next(toCheck.Count); position = toCheck[index]; toCheck[index] = toCheck[^1]; toCheck.RemoveAt(toCheck.Count - 1); if (!visited.Add(position)) continue; for (int i = 0; i < offsets2D.Length; i++) { var next = position + offsets2D[i]; if (!InBounds(next, layer.GetLength(0))) continue; if (CanWalk(layer, position, next, dirs[i])) { toCheck.Add(next); } } safetyCounter++; if (safetyCounter > layer.Length * 2) break; if (visited.Count >= Math.Pow(layer.GetLength(0) - 1, 2) * accessibilityThreshhold) { result = true; break; } } return result; } public static bool InBounds(Vector2I pos, int layerSize) { return pos.X > 0 && pos.Y > 0 && pos.X < layerSize && pos.Y < layerSize; } public static List GetBorderPossibilities(int x, int z) { bool left = x == 0; bool right = x == GameData.layerSize - 1; bool top = z == 0; bool bottom = z == GameData.layerSize - 1; // Corners if (left && top) return new() { "corner_down_right" }; if (left && bottom) return new() { "corner_up_right" }; if (right && top) return new() { "corner_down_left" }; if (right && bottom) return new() { "corner_up_left" }; // Edges if (top) return new() { "straight_left_right", "t_down" }; if (bottom) return new() { "straight_left_right", "t_up" }; if (left) return new() { "straight_up_down", "t_right" }; if (right) return new() { "straight_up_down", "t_left" }; return new(); } }