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)
{
parsed.ArgumentsAreFileNames = true;
break;
+ case "-m":
+ case "--markers":
+ parsed.ShowMarkers = true;
+ break;
+
case "-h":
case "--help":
PrintHelp();
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");
}
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;
_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();
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))
.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));
}
}
using System;
using System.Collections.Generic;
+using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
_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();
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)
{
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)
_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();
// 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");
}
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;
{
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();
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;
}
});
+ 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());
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>();
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)
{
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));
});
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.ContentPack;
_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();
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));
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);
}
}
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);
};
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);