using Godot;
using TheLegendOfGustav.Entities;
using TheLegendOfGustav.Entities.Actors;
using TheLegendOfGustav.Entities.Items;
namespace TheLegendOfGustav.Map;
///
/// 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
{
#region Fields
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");
///
/// Peso do ator no pathfinder.
/// A IA irá evitar de passar por espaços com peso alto.
///
private static readonly float EntityWeight = 10.0f;
#endregion
#region Constructor
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.MapData = this;
InsertEntity(player);
SetupTiles();
}
#endregion
#region Signals
[Signal]
public delegate void EntityPlacedEventHandler(Entity entity);
#endregion
#region Properties
///
/// 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 todas as entidades dentro do mapa.
///
public Godot.Collections.Array Entities { get; private set; } = [];
///
/// Lista de todos os itens dentro do mapa.
///
public Godot.Collections.Array Items
{
get
{
Godot.Collections.Array list = [];
foreach (Entity entity in Entities)
{
if (entity is ConsumableItem item)
{
list.Add(item);
}
}
return list;
}
}
///
/// Objeto do Godot que utiliza do algoritmo A* para calcular
/// caminhos e rotas.
///
public AStarGrid2D Pathfinder { get; private set; }
#endregion
#region Methods
///
/// 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);
}
///
/// 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);
}
///
/// 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 o primeiro item na posição especificada.
///
/// Posição
/// O primeiro item na posição, nulo se não houver.
public ConsumableItem GetFirstItemAtPosition(Vector2I pos)
{
foreach (ConsumableItem item in Items)
{
if (item.GridPosition == pos)
{
return item;
}
}
return null;
}
///
/// Remove uma entidade do mapa sem dar free.
///
/// A entidade para remover
public void RemoveEntity(Entity entity)
{
// Eu removo a entidade do nó de entidades.
entity.GetParent().RemoveChild(entity);
// Eu removo a entidade da lista de entidades do mapa.
Entities.Remove(entity);
}
///
/// 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;
}
///
/// 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));
}
}
}
///
/// 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;
}
#endregion
}