]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Map renderer rework (#37306)
authorVlad <CyberTropic@users.noreply.github.com>
Sun, 11 May 2025 14:06:09 +0000 (10:06 -0400)
committerGitHub <noreply@github.com>
Sun, 11 May 2025 14:06:09 +0000 (00:06 +1000)
* Update TilePainter.cs

* Add support for custom offsets, grid files, and markers

* Dynamic file category handling

Content.MapRenderer/CommandLineArguments.cs
Content.MapRenderer/Painters/DecalPainter.cs
Content.MapRenderer/Painters/EntityPainter.cs
Content.MapRenderer/Painters/GridPainter.cs
Content.MapRenderer/Painters/MapPainter.cs
Content.MapRenderer/Painters/TilePainter.cs
Content.MapRenderer/Program.cs

index b0db9298f818d02ed52e77847e77ad15878e9cb2..f75b671dcb1622cbf1dd3e1462fbd3fdc4ed0c2f 100644 (file)
@@ -12,6 +12,7 @@ public sealed class CommandLineArguments
     public bool ExportViewerJson { get; set; } = false;
     public string OutputPath { get; set; } = DirectoryExtensions.MapImages().FullName;
     public bool ArgumentsAreFileNames { get; set; } = false;
+    public bool ShowMarkers { get; set; } = false;
 
     public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArguments? parsed)
     {
@@ -59,6 +60,11 @@ public sealed class CommandLineArguments
                     parsed.ArgumentsAreFileNames = true;
                     break;
 
+                case "-m":
+                case "--markers":
+                    parsed.ShowMarkers = true;
+                    break;
+
                 case "-h":
                 case "--help":
                     PrintHelp();
@@ -95,7 +101,9 @@ Options:
         Defaults to Resources/MapImages
     -f / --files
         This option tells the map renderer that you supplied a list of map file names instead of their ids.
-        Example: Content.MapRenderer -f box.yml bagel.yml
+        Example: Content.MapRenderer -f /Maps/box.yml /Maps/bagel.yml
+    -m / --markers
+        Show hidden markers on map render. Defaults to false.
     -h / --help
         Displays this help text");
     }
index 6dfb26e1ef2f92fb0ea2cfde51abf7901ab7a9ef..acc9e5615a399b98923bc753e6f2ff4872b97274 100644 (file)
@@ -1,7 +1,9 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Numerics;
 using Content.Shared.Decals;
+using Robust.Client.Graphics;
 using Robust.Client.ResourceManagement;
 using Robust.Client.Utility;
 using Robust.Shared.ContentPack;
@@ -29,7 +31,7 @@ public sealed class DecalPainter
         _sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
     }
 
-    public void Run(Image canvas, Span<DecalData> decals)
+    public void Run(Image canvas, Span<DecalData> decals, Vector2 customOffset = default)
     {
         var stopwatch = new Stopwatch();
         stopwatch.Start();
@@ -46,13 +48,13 @@ public sealed class DecalPainter
 
         foreach (var decal in decals)
         {
-            Run(canvas, decal);
+            Run(canvas, decal, customOffset);
         }
 
         Console.WriteLine($"{nameof(DecalPainter)} painted {decals.Length} decals in {(int) stopwatch.Elapsed.TotalMilliseconds} ms");
     }
 
-    private void Run(Image canvas, DecalData data)
+    private void Run(Image canvas, DecalData data, Vector2 customOffset = default)
     {
         var decal = data.Decal;
         if (!_decalTextures.TryGetValue(decal.Id, out var sprite))
@@ -94,8 +96,10 @@ public sealed class DecalPainter
             .DrawImage(coloredImage, PixelColorBlendingMode.Multiply, PixelAlphaCompositionMode.SrcAtop, 1.0f)
             .Flip(FlipMode.Vertical));
 
-        // Very unsure why the - 1 is needed in the first place but all decals are off by exactly one pixel otherwise
+        var pointX = (int) data.X + (int) (customOffset.X * EyeManager.PixelsPerMeter);
+        var pointY = (int) data.Y + (int) (customOffset.Y * EyeManager.PixelsPerMeter);
+
         // Woohoo!
-        canvas.Mutate(o => o.DrawImage(image, new Point((int) data.X, (int) data.Y - 1), alpha));
+        canvas.Mutate(o => o.DrawImage(image, new Point(pointX, pointY), alpha));
     }
 }
index 0eef467f9ad55f83165b3a6e0a7663ca1322228d..13c7512ea5a51c6289c445a6f1b9113b47ab44ab 100644 (file)
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Numerics;
 using Robust.Client.GameObjects;
 using Robust.Client.Graphics;
 using Robust.Client.ResourceManagement;
@@ -32,7 +33,7 @@ public sealed class EntityPainter
         _errorImage = Image.Load<Rgba32>(_resManager.ContentFileRead("/Textures/error.rsi/error.png"));
     }
 
-    public void Run(Image canvas, List<EntityData> entities)
+    public void Run(Image canvas, List<EntityData> entities, Vector2 customOffset = default)
     {
         var stopwatch = new Stopwatch();
         stopwatch.Start();
@@ -43,13 +44,13 @@ public sealed class EntityPainter
 
         foreach (var entity in entities)
         {
-            Run(canvas, entity, xformSystem);
+            Run(canvas, entity, xformSystem, customOffset);;
         }
 
         Console.WriteLine($"{nameof(EntityPainter)} painted {entities.Count} entities in {(int) stopwatch.Elapsed.TotalMilliseconds} ms");
     }
 
-    public void Run(Image canvas, EntityData entity, SharedTransformSystem xformSystem)
+    public void Run(Image canvas, EntityData entity, SharedTransformSystem xformSystem, Vector2 customOffset = default)
     {
         if (!entity.Sprite.Visible || entity.Sprite.ContainerOccluded)
         {
@@ -135,8 +136,8 @@ public sealed class EntityPainter
             coloredImage.Mutate(o => o.BackgroundColor(imageColor));
 
             var (imgX, imgY) = rsi?.Size ?? (EyeManager.PixelsPerMeter, EyeManager.PixelsPerMeter);
-            var offsetX = (int) (entity.Sprite.Offset.X * EyeManager.PixelsPerMeter);
-            var offsetY = (int) (entity.Sprite.Offset.Y * EyeManager.PixelsPerMeter);
+            var offsetX = (int) (entity.Sprite.Offset.X + customOffset.X) * EyeManager.PixelsPerMeter;
+            var offsetY = (int) (entity.Sprite.Offset.Y + customOffset.X) * EyeManager.PixelsPerMeter;
             image.Mutate(o => o
                 .DrawImage(coloredImage, PixelColorBlendingMode.Multiply, PixelAlphaCompositionMode.SrcAtop, 1)
                 .Resize(imgX, imgY)
index d8d6e15378c29fe64c84c6c136a2736b1e63cd0b..ed17d0d3d66ec60b27befa809ecb3fe0def23804 100644 (file)
@@ -43,7 +43,7 @@ namespace Content.MapRenderer.Painters
             _decals = GetDecals();
         }
 
-        public void Run(Image gridCanvas, EntityUid gridUid, MapGridComponent grid)
+        public void Run(Image gridCanvas, EntityUid gridUid, MapGridComponent grid, Vector2 customOffset = default)
         {
             var stopwatch = new Stopwatch();
             stopwatch.Start();
@@ -56,10 +56,10 @@ namespace Content.MapRenderer.Painters
 
             // Decals are always painted before entities, and are also optional.
             if (_decals.TryGetValue(gridUid, out var decals))
-                _decalPainter.Run(gridCanvas, CollectionsMarshal.AsSpan(decals));
+                _decalPainter.Run(gridCanvas, CollectionsMarshal.AsSpan(decals), customOffset);
 
 
-            _entityPainter.Run(gridCanvas, entities);
+            _entityPainter.Run(gridCanvas, entities, customOffset);
             Console.WriteLine($"{nameof(GridPainter)} painted grid {gridUid} in {(int) stopwatch.Elapsed.TotalMilliseconds} ms");
         }
 
index 94d58b35a001c63e8bb898bcceefa48f4c966572..795cbc04ee3a73d0e75041385665b56075e03410 100644 (file)
@@ -1,19 +1,23 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Numerics;
+using System.IO;
 using System.Threading.Tasks;
 using Content.IntegrationTests;
 using Content.Server.GameTicking;
-using Content.Server.Maps;
 using Robust.Client.GameObjects;
 using Robust.Server.GameObjects;
 using Robust.Server.Player;
+using Robust.Shared.EntitySerialization;
+using Robust.Shared.EntitySerialization.Systems;
 using Robust.Shared.GameObjects;
 using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
+using Robust.Shared.Map.Events;
 using Robust.Shared.Maths;
-using Robust.Shared.Prototypes;
 using Robust.Shared.Timing;
+using Robust.Shared.Utility;
 using SixLabors.ImageSharp;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
@@ -22,7 +26,9 @@ namespace Content.MapRenderer.Painters
 {
     public sealed class MapPainter
     {
-        public static async IAsyncEnumerable<RenderedGridImage<Rgba32>> Paint(string map)
+        public static async IAsyncEnumerable<RenderedGridImage<Rgba32>> Paint(string map,
+            bool mapIsFilename = false,
+            bool showMarkers = false)
         {
             var stopwatch = new Stopwatch();
             stopwatch.Start();
@@ -33,7 +39,7 @@ namespace Content.MapRenderer.Painters
                 Connected = true,
                 Fresh = true,
                 // Seriously whoever made MapPainter use GameMapPrototype I wish you step on a lego one time.
-                Map = map,
+                Map = mapIsFilename ? "Empty" : map,
             });
 
             var server = pair.Server;
@@ -54,9 +60,89 @@ namespace Content.MapRenderer.Painters
                 }
             });
 
+            if (showMarkers)
+                await pair.WaitClientCommand("showmarkers");
+
             var sEntityManager = server.ResolveDependency<IServerEntityManager>();
             var sPlayerManager = server.ResolveDependency<IPlayerManager>();
 
+            var entityManager = server.ResolveDependency<IEntityManager>();
+            var mapLoader = entityManager.System<MapLoaderSystem>();
+            var mapSys = entityManager.System<SharedMapSystem>();
+            var deps = server.ResolveDependency<IEntitySystemManager>().DependencyCollection;
+
+            Entity<MapGridComponent>[] grids = [];
+
+            if (mapIsFilename)
+            {
+                var resPath = new ResPath(map);
+
+                if (!mapLoader.TryReadFile(resPath, out var data))
+                    throw new IOException($"File {map} could not be read");
+
+                var ev = new BeforeEntityReadEvent();
+                server.EntMan.EventBus.RaiseEvent(EventSource.Local, ev);
+
+                var deserializer = new EntityDeserializer(deps,
+                    data,
+                    DeserializationOptions.Default,
+                    ev.RenamedPrototypes,
+                    ev.DeletedPrototypes);
+
+                if (!deserializer.TryProcessData())
+                {
+                    throw new IOException($"Failed to process entity data in {map}");
+                }
+
+                if (deserializer.Result.Category == FileCategory.Unknown && deserializer.Result.Version < 7)
+                {
+                    var mapCount = 0;
+                    var gridCount = 0;
+                    foreach (var (entId, ent) in deserializer.YamlEntities)
+                    {
+                        if (ent.Components != null && ent.Components.ContainsKey("MapGrid"))
+                        {
+                            gridCount++;
+                        }
+                        if (ent.Components != null && ent.Components.ContainsKey("Map"))
+                        {
+                            mapCount++;
+                        }
+                    }
+                    if (mapCount == 1)
+                        deserializer.Result.Category = FileCategory.Map;
+                    else if (mapCount == 0 && gridCount == 1)
+                        deserializer.Result.Category = FileCategory.Grid;
+                }
+
+                switch (deserializer.Result.Category)
+                {
+                    case FileCategory.Map:
+                        await server.WaitPost(() =>
+                        {
+                            if (mapLoader.TryLoadMap(resPath, out _, out var loadedGrids))
+                            {
+                                grids = loadedGrids.ToArray();
+                            }
+                        });
+                        break;
+
+                    case FileCategory.Grid:
+                        await server.WaitPost(() =>
+                        {
+                            if (mapLoader.TryLoadGrid(resPath, out _, out var loadedGrids))
+                            {
+                                grids = [(Entity<MapGridComponent>)loadedGrids];
+                            }
+                        });
+                        break;
+
+                    default:
+                        throw new IOException($"Unknown category {deserializer.Result.Category}");
+
+                }
+            }
+
             await pair.RunTicksSync(10);
             await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
 
@@ -64,7 +150,6 @@ namespace Content.MapRenderer.Painters
 
             var tilePainter = new TilePainter(client, server);
             var entityPainter = new GridPainter(client, server);
-            Entity<MapGridComponent>[] grids = null!;
             var xformQuery = sEntityManager.GetEntityQuery<TransformComponent>();
             var xformSystem = sEntityManager.System<SharedTransformSystem>();
 
@@ -77,8 +162,11 @@ namespace Content.MapRenderer.Painters
                     sEntityManager.DeleteEntity(playerEntity.Value);
                 }
 
-                var mapId = sEntityManager.System<GameTicker>().DefaultMap;
-                grids = sMapManager.GetAllGrids(mapId).ToArray();
+                if (!mapIsFilename)
+                {
+                    var mapId = sEntityManager.System<GameTicker>().DefaultMap;
+                    grids = sMapManager.GetAllGrids(mapId).ToArray();
+                }
 
                 foreach (var (uid, _) in grids)
                 {
@@ -92,32 +180,33 @@ namespace Content.MapRenderer.Painters
 
             foreach (var (uid, grid) in grids)
             {
-                // Skip empty grids
-                if (grid.LocalAABB.IsEmpty())
+                var tiles = mapSys.GetAllTiles(uid, grid).ToList();
+                if (tiles.Count == 0)
                 {
                     Console.WriteLine($"Warning: Grid {uid} was empty. Skipping image rendering.");
                     continue;
                 }
-
                 var tileXSize = grid.TileSize * TilePainter.TileImageSize;
                 var tileYSize = grid.TileSize * TilePainter.TileImageSize;
 
-                var bounds = grid.LocalAABB;
-
-                var left = bounds.Left;
-                var right = bounds.Right;
-                var top = bounds.Top;
-                var bottom = bounds.Bottom;
+                var minX = tiles.Min(t => t.X);
+                var minY = tiles.Min(t => t.Y);
+                var maxX = tiles.Max(t => t.X);
+                var maxY = tiles.Max(t => t.Y);
+                var w = (maxX - minX + 1) * tileXSize;
+                var h = (maxY - minY + 1) * tileYSize;
+                var customOffset = new Vector2();
 
-                var w = (int) Math.Ceiling(right - left) * tileXSize;
-                var h = (int) Math.Ceiling(top - bottom) * tileYSize;
+                //MapGrids don't have LocalAABB, so we offset them to align the bottom left corner with 0,0 coordinates
+                if (grid.LocalAABB.IsEmpty())
+                    customOffset = new Vector2(-minX, -minY);
 
                 var gridCanvas = new Image<Rgba32>(w, h);
 
                 await server.WaitPost(() =>
                 {
-                    tilePainter.Run(gridCanvas, uid, grid);
-                    entityPainter.Run(gridCanvas, uid, grid);
+                    tilePainter.Run(gridCanvas, uid, grid, customOffset);
+                    entityPainter.Run(gridCanvas, uid, grid, customOffset);
 
                     gridCanvas.Mutate(e => e.Flip(FlipMode.Vertical));
                 });
index 7084d2caf4a55a5e09741fa9a97348dadc93d26f..4856c181da62f67b6311722fdc35f128570942af 100644 (file)
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Numerics;
 using Robust.Client.Graphics;
 using Robust.Client.ResourceManagement;
 using Robust.Shared.ContentPack;
@@ -31,7 +32,7 @@ namespace Content.MapRenderer.Painters
             _sMapSystem = esm.GetEntitySystem<SharedMapSystem>();
         }
 
-        public void Run(Image gridCanvas, EntityUid gridUid, MapGridComponent grid)
+        public void Run(Image gridCanvas, EntityUid gridUid, MapGridComponent grid, Vector2 customOffset = default)
         {
             var stopwatch = new Stopwatch();
             stopwatch.Start();
@@ -51,8 +52,8 @@ namespace Content.MapRenderer.Painters
                 if (string.IsNullOrWhiteSpace(path))
                     return;
 
-                var x = (int) (tile.X + xOffset);
-                var y = (int) (tile.Y + yOffset);
+                var x = (int) (tile.X + xOffset + customOffset.X);
+                var y = (int) (tile.Y + yOffset + customOffset.Y);
                 var image = images[path][tile.Tile.Variant];
 
                 gridCanvas.Mutate(o => o.DrawImage(image, new Point(x * tileSize, y * tileSize), 1));
@@ -93,7 +94,7 @@ namespace Content.MapRenderer.Painters
                 for (var i = 0; i < definition.Variants; i++)
                 {
                     var index = i;
-                    var tileImage = tileSheet.Clone(o => o.Crop(new Rectangle(tileSize * index, 0, 32, 32)));
+                    var tileImage = tileSheet.Clone(o => o.Crop(new Rectangle(tileSize * index, 0, 32, 32)).Flip(FlipMode.Vertical));
                     images[path].Add(tileImage);
                 }
             }
index 5a4ccda0c89a53eaf16ab1a0f19f1fec65f3a6e7..115d83e65e08030a088219ee7dac6af158ee53e8 100644 (file)
@@ -106,34 +106,7 @@ namespace Content.MapRenderer
 
             if (arguments.ArgumentsAreFileNames)
             {
-                Console.WriteLine("Retrieving map ids by map file names...");
-
-                Console.Write("Fetching map prototypes... ");
-                await using var pair = await PoolManager.GetServerClient();
-                var mapPrototypes = pair.Server
-                    .ResolveDependency<IPrototypeManager>()
-                    .EnumeratePrototypes<GameMapPrototype>()
-                    .ToArray();
-                Console.WriteLine("[Done]");
-
-                var ids = new List<string>();
-
-                foreach (var mapPrototype in mapPrototypes)
-                {
-                    if (arguments.Maps.Contains(mapPrototype.MapPath.Filename))
-                    {
-                        ids.Add(mapPrototype.ID);
-                        Console.WriteLine($"Found map: {mapPrototype.MapName}");
-                    }
-                }
-
-                if (ids.Count == 0)
-                {
-                    await Console.Error.WriteLineAsync("Found no maps for the given file names!");
-                    return;
-                }
-
-                arguments.Maps = ids;
+                Console.WriteLine("Retrieving maps by file names...");
             }
 
             await Run(arguments);
@@ -156,12 +129,14 @@ namespace Content.MapRenderer
                 };
 
                 mapViewerData.ParallaxLayers.Add(LayerGroup.DefaultParallax());
-                var directory = Path.Combine(arguments.OutputPath, map);
+                var directory = Path.Combine(arguments.OutputPath, Path.GetFileNameWithoutExtension(map));
 
                 var i = 0;
                 try
                 {
-                    await foreach (var renderedGrid in MapPainter.Paint(map))
+                    await foreach (var renderedGrid in MapPainter.Paint(map,
+                                       arguments.ArgumentsAreFileNames,
+                                       arguments.ShowMarkers))
                     {
                         var grid = renderedGrid.Image;
                         Directory.CreateDirectory(directory);