using Godot;
///
/// Classe que cuida dos dados e da parte lógica do mapa.
/// O mapa é o cenário onde as ações do jogo ocorrem.
/// Mais especificamente, o mapa é um único andar da masmorra.
///
public partial class MapData : RefCounted
{
public static readonly TileDefinition wallDefinition = GD.Load("res://assets/definitions/tiles/wall.tres");
public static readonly TileDefinition floorDefinition = GD.Load("res://assets/definitions/tiles/floor.tres");
///
/// Largura do mapa.
///
public int Width { get; private set; }
///
/// Altura do mapa.
///
public int Height { get; private set; }
///
/// Os tiles que compõem o mapa.
///
public Godot.Collections.Array Tiles { get; private set; } = [];
///
/// O jogador é especial e por isso o mapa faz questão de rastreá-lo.
///
public Player Player { get; set; }
///
/// Lista de todos os atores dentro do mapa.
///
public Godot.Collections.Array Entities { get; private set; } = [];
private AStarGrid2D pathfinder;
///
/// Objeto do Godot que utiliza do algoritmo A* para calcular
/// caminhos e rotas.
///
public AStarGrid2D Pathfinder { get => pathfinder; }
///
/// Peso do ator no pathfinder.
/// A IA irá evitar de passar por espaços com peso alto.
///
private static readonly float EntityWeight = 10.0f;
///
/// Inicializa o pathfinder;
///
public void SetupPathfinding() {
pathfinder = new AStarGrid2D
{
//A região é o mapa inteiro.
Region = new Rect2I(0, 0, Width, Height)
};
// Atualiza o pathfinder para a região definida.
pathfinder.Update();
// Define quais pontos do mapa são passáveis ou não.
for (int y = 0; y < Height; y++) {
for (int x = 0; x < Width; x++) {
Vector2I pos = new Vector2I(x, y);
Tile tile = GetTile(pos);
// Pontos sólidos são impossíveis de passar.
pathfinder.SetPointSolid(pos, !tile.IsWalkable);
}
}
// Registra todos os atores em cena.
foreach (Entity entity in Entities) {
if (entity.BlocksMovement) {
RegisterBlockingEntity(entity);
}
}
}
///
/// Define um peso na posição de uma entidade para que a IA evite de passar por lá.
/// Ênfase em evitar. Se o único caminho para o destino estiver bloqueado
/// por uma entidade, o jogo tentará andar mesmo assim.
///
/// A entidade em questão.
public void RegisterBlockingEntity(Entity entity) {
pathfinder.SetPointWeightScale(entity.GridPosition, EntityWeight);
}
///
/// Remove o peso na posição de uma entidade.
/// Quando uma entidade move sua posição, devemos tirar o peso de sua posição anterior.
///
/// A entidade em questão.
public void UnregisterBlockingEntity(Entity entity) {
pathfinder.SetPointWeightScale(entity.GridPosition, 0);
}
public MapData(int width, int height, Player player) {
Width = width;
Height = height;
Player = player;
// Como o jogador é criado antes do mapa, precisamos
// atualizá-lo com o novo mapa.
player.Map_Data = this;
InsertEntity(player);
SetupTiles();
}
///
/// Cria novos Tiles até preencher as dimensões do mapa.
/// É importante que estes tiles sejam paredes, o gerador de mapas
/// não cria paredes por conta própria.
///
private void SetupTiles() {
for (int i = 0; i < Height; i++)
{
for (int j = 0; j < Width; j++)
{
Tiles.Add(new Tile(new Vector2I(j, i), wallDefinition));
}
}
}
///
/// Registra uma entidade no mapa. A existência de uma entidade não é considerada se ela não
/// estiver registrada no mapa.
///
/// A entidade em questão
public void InsertEntity(Entity entity) {
Entities.Add(entity);
}
///
/// Converte uma coordenada em um índice para acessar a lista de tiles.
///
/// Vetor posição
/// Índice na lista de tiles. -1 se estiver fora do mapa.
private int GridToIndex(Vector2I pos) {
if (!IsInBounds(pos)) return -1;
return pos.Y * Width + pos.X;
}
///
/// Se uma coordenada está dentro da área do mapa.
///
/// Vetor posição
/// Se o vetor está dentro do mapa.
private bool IsInBounds(Vector2I pos) {
if (pos.X < 0 || pos.Y < 0) {
return false;
}
if (pos.X >= Width || pos.Y >= Height) {
return false;
}
return true;
}
///
/// Obtém o tile na posição desejada.
///
/// Vetor posição
/// O tile na posição, nulo se for fora do mapa.
public Tile GetTile(Vector2I pos) {
int index = GridToIndex(pos);
if (index < 0) return null;
return Tiles[index];
}
///
/// Obtém o tile na posição desejada.
///
/// x da coordenada
/// y da coordenada
/// O tile na posição, nulo se for fora do mapa.
public Tile GetTile(int x, int y) {
return GetTile(new Vector2I(x, y));
}
///
/// Obtém a entidade na posição especificada.
///
/// Vetor posição
/// A entidade na posição especificada, nulo se não houver.
public Entity GetBlockingEntityAtPosition(Vector2I pos) {
foreach (Entity entity in Entities) {
if (entity.GridPosition == pos && entity.BlocksMovement) {
return entity;
}
}
return null;
}
///
/// Obtém todas as entidades na posição especificada.
/// É possível haver mais de uma entidade na mesma posição se uma delas não bloquear movimento.
///
/// Vetor posição
/// Lista com todas as entidades na posição especificada.
public Godot.Collections.Array GetEntitiesAtPosition(Vector2I pos) {
Godot.Collections.Array ZOfZero = [];
Godot.Collections.Array ZOfOne = [];
Godot.Collections.Array ZOfTwo = [];
// Pego todos os atores
foreach (Entity entity in Entities) {
if (entity.GridPosition == pos) {
switch (entity.ZIndex) {
case 0:
ZOfZero.Add(entity);
break;
case 1:
ZOfOne.Add(entity);
break;
case 2:
ZOfTwo.Add(entity);
break;
}
}
}
// Retorno os atores ordenados por ZIndex.
return ZOfZero + ZOfOne + ZOfTwo;
}
}