263 lines
5.6 KiB
C#
263 lines
5.6 KiB
C#
using Godot;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using static WFC;
|
|
public partial class Layer : Node3D
|
|
{
|
|
private Node3D decorationRoot;
|
|
public Tile[,] tiles;
|
|
int layerSize;
|
|
Tile tile;
|
|
int level;
|
|
bool updateFailed = 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.DecorationNode.GetChildren())
|
|
{
|
|
child.QueueFree();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetupLayer(int layerSize, int level, Dictionary<string, MeshInstance3D> tileMeshes)
|
|
{
|
|
this.layerSize = layerSize;
|
|
this.level = level;
|
|
tiles = new Tile[layerSize, layerSize];
|
|
GenerateBaseStructure(tileMeshes);
|
|
int safetyCounter = 0;
|
|
while (true)
|
|
{
|
|
if (GenerateLayer())
|
|
{
|
|
GD.Print("Layer generated");
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
GD.PrintErr("Layer failed... trying again");
|
|
}
|
|
ResetLayer(tileMeshes);
|
|
safetyCounter++;
|
|
if (safetyCounter > 1000) break;
|
|
}
|
|
CreateTileNodes();
|
|
}
|
|
|
|
private void GenerateBaseStructure(Dictionary<string, MeshInstance3D> tileMeshes)
|
|
{
|
|
Vector3 position;
|
|
float offsetX;
|
|
float offsetY = level * 4 * -1;
|
|
float offsetZ;
|
|
for (int x = 0; x < layerSize; x++)
|
|
{
|
|
offsetX = x * 4;
|
|
for (int y = 0; y < layerSize; y++)
|
|
{
|
|
offsetZ = y * 4;
|
|
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.DecorationNode = node;
|
|
}
|
|
}
|
|
|
|
private void ResetLayer(Dictionary<string, MeshInstance3D> 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];
|
|
|
|
string result = tile.Collapse("border");
|
|
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 = "";
|
|
if (position.X == 0 || position.X == layerSize - 1 || position.Y == 0 || position.Y == layerSize - 1)
|
|
{
|
|
keyword = tiles[position.X, position.Y].Collapse("border");
|
|
}
|
|
else
|
|
{
|
|
keyword = tiles[position.X, position.Y].Collapse("");
|
|
}
|
|
if (keyword == "ERR")
|
|
{
|
|
GD.Print("Error in WFC during collapse!");
|
|
return false;
|
|
}
|
|
if (keyword != "")
|
|
{
|
|
NewPropagate(position);
|
|
if (updateFailed) break;
|
|
position = GetSmallestPossibilities();
|
|
if (position == new Vector2(-100, -100))
|
|
{
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
safetyCounter++;
|
|
if (safetyCounter == layerSize * layerSize)
|
|
{
|
|
GD.Print("Error in WFC, overflow!");
|
|
return false;
|
|
}
|
|
}
|
|
if (updateFailed) return false;
|
|
//Spawn is a tile border, redo world generation
|
|
if (tiles[1, 1].collapsedMesh == "border") 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<Vector2I> queue = new Queue<Vector2I>();
|
|
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<string> { currentTile.collapsedMesh }
|
|
: new HashSet<string>(currentTile.tileMeshes.Keys);
|
|
|
|
for (int i = 0; i < dirs.Length; i++)
|
|
{
|
|
Vector2I newPos = currentPos + offsets[i];
|
|
if (!InBounds(newPos, layerSize, true)) continue;
|
|
|
|
Tile neighborTile = tiles[newPos.X, newPos.Y];
|
|
|
|
HashSet<string> allowed = new HashSet<string>();
|
|
|
|
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)
|
|
{
|
|
GD.Print("WFC Error! No meshes left");
|
|
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;
|
|
}
|
|
}
|