From c6bbb834f7758027c0df338f1520f34fad3befea Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 9 Sep 2025 19:09:34 -0300 Subject: Organização MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Map/MapData.cs | 318 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 scripts/Map/MapData.cs (limited to 'scripts/Map/MapData.cs') diff --git a/scripts/Map/MapData.cs b/scripts/Map/MapData.cs new file mode 100644 index 0000000..3365380 --- /dev/null +++ b/scripts/Map/MapData.cs @@ -0,0 +1,318 @@ +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 +} -- cgit v1.2.3