diff --git a/Scripts/Tests/TestRunner.cs b/Scripts/Tests/TestRunner.cs index 82f3645..d76916d 100644 --- a/Scripts/Tests/TestRunner.cs +++ b/Scripts/Tests/TestRunner.cs @@ -474,9 +474,9 @@ public partial class TestRunner : Node amount = 2 }; - AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "first iteration"); - AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "second iteration"); - AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "loop finished"); + AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "first iteration"); + AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "second iteration"); + AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "loop finished"); } private void TestStartNodeSucceedsImmediately() diff --git a/Scripts/UI/Common/UIStyle.cs b/Scripts/UI/Common/UIStyle.cs index 30709db..5f3defd 100644 --- a/Scripts/UI/Common/UIStyle.cs +++ b/Scripts/UI/Common/UIStyle.cs @@ -154,4 +154,9 @@ public static class UIStyle { return Warning; } + + public static Color GetBorderColor() + { + return Accent; + } } diff --git a/Scripts/World/Map.cs b/Scripts/World/Map.cs index 7a2f2ac..4d92fa2 100644 --- a/Scripts/World/Map.cs +++ b/Scripts/World/Map.cs @@ -4,38 +4,43 @@ 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; - Dictionary> visibleRobots = new Dictionary>(); - - Vector3I tilePos; - Vector2I key; - - foreach(Robot robot in GameData.robots) - { - if(!robot.showOnMap) continue; - tilePos = Pathfinding.GetClosestStartPoint(robot.Position); - if(tilePos.Y != GameData.visibleLayer) continue; - key = new Vector2I(tilePos.X, tilePos.Z); - if(!visibleRobots.ContainsKey(key)) visibleRobots.Add(key, new List()); - visibleRobots[key].Add(robot); - } + visibleRobots = BuildVisibleRobots(); for (int z = -1; z < size; z++) { @@ -47,7 +52,8 @@ public partial class Map : PanelContainer continue; } - TextureRect texture = CreateTileTexture(tiles[x, z]); + 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])) @@ -65,6 +71,116 @@ public partial class Map : PanelContainer } } + 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()) @@ -91,7 +207,7 @@ public partial class Map : PanelContainer return label; } - private TextureRect CreateTileTexture(Tile tile) + private TextureRect CreateTileTexture(Tile tile, List robotsOnTile) { TextureRect texture = new TextureRect { @@ -100,7 +216,7 @@ public partial class Map : PanelContainer StretchMode = TextureRect.StretchModeEnum.Scale }; - UpdateTileTexture(tile, texture); + UpdateTileTexture(tile, texture, robotsOnTile); return texture; } @@ -121,26 +237,41 @@ public partial class Map : PanelContainer { if (!IsInstanceValid(texture)) return; - UpdateTileTexture(tile, texture); + UpdateTileTexture(tile, texture, GetRobotsOnTile( + BuildVisibleRobots(), + tile.GridPosition + )); } - private void UpdateTileTexture(Tile tile, TextureRect texture) + private void UpdateTileTexture(Tile tile, TextureRect texture, List robotsOnTile) { + Texture2D tileTexture; + string tooltipText; + if (!tile.wasVisited && !GameData.debugMode) { - texture.Texture = GenerateTexture(32, new Color(0, 0, 0, 1)); - texture.TooltipText = "Not explored"; + tileTexture = GenerateTexture(TileTextureSize, new Color(0, 0, 0, 1)); + tooltipText = "Not explored"; } else if (tile.containsResource) { - texture.Texture = ResourceDistributor.resources[tile.resource.name]; - texture.TooltipText = tile.resource.item.GetReadableName() + $"\r(X: {tile.GridPosition.X},Y: {GameData.currentLayer},Z: {tile.GridPosition.Y})"; + tileTexture = ResourceDistributor.resources[tile.resource.name]; + tooltipText = tile.resource.item.GetReadableName() + GetPositionTooltip(tile); } else { - texture.Texture = GenerateTexture(32, new Color(0, 0, 0, 0)); - texture.TooltipText = ""; + 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); + } + + texture.Texture = tileTexture; + texture.TooltipText = tooltipText; } public Texture2D GenerateTexture(int size, Color fillColor) @@ -150,4 +281,59 @@ public partial class Map : PanelContainer 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; + } }