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 ItemEntity 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 ItemEntity GetFirstItemAtPosition(Vector2I pos) { foreach (ItemEntity 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 }