Files
RuinAdventurer/Scripts/World/Layer.cs
T
Nicola 8170b700b2 Added final features for this release. Now only polishing (if needed) remains.
Features: Sacrifice-Node, Maintain-Node, Options for screen type, lightcolor and soundvolume, tied in sound effects, game pause when menu is open, visibly open up gate when opening it.
2026-05-10 14:09:14 +02:00

288 lines
6.3 KiB
C#

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<string> currentResources;
public bool isGateOpen = false;
public List<Ingredient> gateIngredients = new();
public override void _Ready()
{
currentResources = new List<string>();
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<string, MeshInstance3D> 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<string, MeshInstance3D> 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<string, MeshInstance3D> 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<string> 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<Vector2I> queue = new Queue<Vector2I>();
queue.Enqueue(startPos);
while (queue.Count > 0)
{
Vector2I currentPos = queue.Dequeue();
Tile currentTile = tiles[currentPos.X, currentPos.Y];
HashSet<string> currentPossibilities = currentTile.collapsedMesh != null
? new HashSet<string> { currentTile.collapsedMesh }
: new HashSet<string>(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<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)
{
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;
}
}