using Godot; using System; using System.Collections.Generic; public partial class Map : PanelContainer { private const int TileTextureSize = 32; private const int RobotBorderWidth = 2; private const double RobotMapUpdateInterval = 0.2; [Export] GridContainer grid; private HashSet handledTiles = new HashSet(); private Dictionary textureMap = new Dictionary(); private Dictionary> visibleRobots = new Dictionary>(); private double robotMapUpdateTimer = 0.0; public override void _Ready() { grid.Columns = GameData.layerSize + 1; } public override void _Process(double delta) { if (!Visible) return; robotMapUpdateTimer += delta; if (robotMapUpdateTimer < RobotMapUpdateInterval) return; robotMapUpdateTimer = 0.0; UpdateVisibleRobotTiles(); } public void ShowMap() { if (!Visible) return; ClearGrid(); robotMapUpdateTimer = 0.0; Tile[,] tiles = GameData.map[GameData.currentLayer].tiles; int size = GameData.layerSize; visibleRobots = BuildVisibleRobots(); for (int z = -1; z < size; z++) { for (int x = -1; x < size; x++) { if (z == -1 || x == -1) { grid.AddChild(CreateHeaderLabel(x, z)); continue; } Vector2I tilePosition = new Vector2I(x, z); TextureRect texture = CreateTileTexture(tiles[x, z], GetRobotsOnTile(visibleRobots, tilePosition)); textureMap[tiles[x, z]] = texture; if (!handledTiles.Contains(tiles[x, z])) { tiles[x, z].OnTileVisited += OnTileVisited; handledTiles.Add(tiles[x, z]); } texture.SizeFlagsHorizontal = SizeFlags.ExpandFill; texture.SizeFlagsVertical = SizeFlags.ExpandFill; texture.StretchMode = TextureRect.StretchModeEnum.Scale; grid.AddChild(texture); } } } private void UpdateVisibleRobotTiles() { Dictionary> updatedVisibleRobots = BuildVisibleRobots(); if (HasSameVisibleRobots(visibleRobots, updatedVisibleRobots)) return; List affectedTiles = GetAffectedRobotTiles(visibleRobots, updatedVisibleRobots); visibleRobots = updatedVisibleRobots; foreach (Vector2I tilePosition in affectedTiles) { UpdateTileAtPosition(tilePosition); } } private bool HasSameVisibleRobots( Dictionary> oldRobots, Dictionary> newRobots ) { if (oldRobots.Count != newRobots.Count) return false; foreach (KeyValuePair> kvp in oldRobots) { if (!newRobots.ContainsKey(kvp.Key)) return false; if (!ContainsSameRobots(kvp.Value, newRobots[kvp.Key])) return false; } return true; } private bool ContainsSameRobots(List oldRobots, List newRobots) { if (oldRobots.Count != newRobots.Count) return false; foreach (Robot robot in oldRobots) { if (!newRobots.Contains(robot)) return false; } return true; } private List GetAffectedRobotTiles( Dictionary> oldRobots, Dictionary> newRobots ) { List affectedTiles = new List(); foreach (Vector2I tilePosition in oldRobots.Keys) { AddAffectedTile(affectedTiles, tilePosition); } foreach (Vector2I tilePosition in newRobots.Keys) { AddAffectedTile(affectedTiles, tilePosition); } return affectedTiles; } private void AddAffectedTile(List affectedTiles, Vector2I tilePosition) { if (affectedTiles.Contains(tilePosition)) return; affectedTiles.Add(tilePosition); } private void UpdateTileAtPosition(Vector2I tilePosition) { if (tilePosition.X < 0 || tilePosition.X >= GameData.layerSize) return; if (tilePosition.Y < 0 || tilePosition.Y >= GameData.layerSize) return; Tile tile = GameData.map[GameData.currentLayer].tiles[tilePosition.X, tilePosition.Y]; if (!textureMap.ContainsKey(tile)) return; UpdateMap(tile, textureMap[tile]); } private Dictionary> BuildVisibleRobots() { Dictionary> visibleRobots = new Dictionary>(); foreach (Robot robot in GameData.robots) { if (!robot.showOnMap) continue; Vector3I tilePos = Pathfinding.GetClosestStartPoint(robot.Position); if (tilePos.Y != GameData.currentLayer) continue; Vector2I key = new Vector2I(tilePos.X, tilePos.Z); if (!visibleRobots.ContainsKey(key)) { visibleRobots.Add(key, new List()); } visibleRobots[key].Add(robot); } return visibleRobots; } private List GetRobotsOnTile(Dictionary> visibleRobots, Vector2I tilePosition) { if (!visibleRobots.ContainsKey(tilePosition)) return new List(); return visibleRobots[tilePosition]; } private void ClearGrid() { foreach (Node node in grid.GetChildren()) { grid.RemoveChild(node); node.QueueFree(); } } private Label CreateHeaderLabel(int x, int z) { Label label = new Label { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, SizeFlagsHorizontal = SizeFlags.ExpandFill, SizeFlagsVertical = SizeFlags.ExpandFill }; if (z == -1 && x == -1) label.Text = "\u2193 Z | \u2192 X"; else if (z == -1) label.Text = x.ToString(); else label.Text = z.ToString(); return label; } private TextureRect CreateTileTexture(Tile tile, List robotsOnTile) { TextureRect texture = new TextureRect { SizeFlagsHorizontal = SizeFlags.ExpandFill, SizeFlagsVertical = SizeFlags.ExpandFill, StretchMode = TextureRect.StretchModeEnum.Scale }; UpdateTileTexture(tile, texture, robotsOnTile); return texture; } private void OnTileVisited(object sender, EventArgs e) { Tile tile = sender as Tile; if (tile == null) return; if (textureMap.TryGetValue(tile, out TextureRect texture)) { UpdateMap(tile, texture); } } public void UpdateMap(Tile tile, TextureRect texture) { if (!IsInstanceValid(texture)) return; UpdateTileTexture(tile, texture, GetRobotsOnTile( BuildVisibleRobots(), tile.GridPosition )); } private void UpdateTileTexture(Tile tile, TextureRect texture, List robotsOnTile) { Texture2D tileTexture; string tooltipText; if (!tile.wasVisited && !GameData.debugMode) { tileTexture = GenerateTexture(TileTextureSize, new Color(0, 0, 0, 1)); tooltipText = "Not explored"; } else if (tile.containsResource) { tileTexture = ResourceDistributor.resources[tile.resource.name]; tooltipText = tile.resource.item.GetReadableName() + GetPositionTooltip(tile); } else { tileTexture = GenerateTexture(TileTextureSize, new Color(0, 0, 0, 0)); tooltipText = ""; } if (robotsOnTile.Count > 0) { tileTexture = AddBorder(tileTexture, UIStyle.GetBorderColor(), RobotBorderWidth); tooltipText += GetRobotTooltip(robotsOnTile, tooltipText.Length > 0); } if (tile.collapsedMesh == "gate" && tile.wasVisited) { tileTexture = AddBorder(tileTexture, Colors.Red, RobotBorderWidth); } texture.Texture = tileTexture; texture.TooltipText = tooltipText; } public Texture2D GenerateTexture(int size, Color fillColor) { Image image = Image.CreateEmpty(size, size, false, Image.Format.Rgba8); image.Fill(fillColor); return ImageTexture.CreateFromImage(image); } public Texture2D AddBorder(Texture2D texture, Color borderColor, int borderWidth) { if (texture == null) return null; if (borderWidth <= 0) return texture; Image sourceImage = texture.GetImage(); if (sourceImage == null) return texture; if (sourceImage.GetFormat() != Image.Format.Rgba8) { sourceImage.Convert(Image.Format.Rgba8); } int width = sourceImage.GetWidth(); int height = sourceImage.GetHeight(); int actualBorderWidth = Math.Min(borderWidth, Math.Min(width, height) / 2); for (int x = 0; x < width; x++) { for (int y = 0; y < actualBorderWidth; y++) { sourceImage.SetPixel(x, y, borderColor); sourceImage.SetPixel(x, height - 1 - y, borderColor); } } for (int y = actualBorderWidth; y < height - actualBorderWidth; y++) { for (int x = 0; x < actualBorderWidth; x++) { sourceImage.SetPixel(x, y, borderColor); sourceImage.SetPixel(width - 1 - x, y, borderColor); } } return ImageTexture.CreateFromImage(sourceImage); } private string GetPositionTooltip(Tile tile) { return $"\r(X: {tile.GridPosition.X},Y: {GameData.currentLayer},Z: {tile.GridPosition.Y})"; } private string GetRobotTooltip(List robotsOnTile, bool addSeparator) { string tooltip = addSeparator ? "\rRobots:" : "Robots:"; foreach (Robot robot in robotsOnTile) { tooltip += "\r- " + robot.Name; } return tooltip; } }