diff --git a/Makefile b/Makefile index 706f87adba..08505885fd 100644 --- a/Makefile +++ b/Makefile @@ -166,7 +166,7 @@ mod_ts: $(mod_ts_TARGET) editor_SRCS := $(shell find OpenRA.Editor/ -iname '*.cs') editor_TARGET = OpenRA.Editor.exe editor_KIND = winexe -editor_DEPS = $(game_TARGET) +editor_DEPS = $(game_TARGET) $(mod_common_TARGET) editor_LIBS = System.Windows.Forms.dll System.Data.dll System.Drawing.dll $(editor_DEPS) thirdparty/Eluant.dll editor_EXTRA = -resource:OpenRA.Editor.Form1.resources -resource:OpenRA.Editor.MapSelect.resources editor_FLAGS = -win32icon:OpenRA.Editor/OpenRA.Editor.Icon.ico diff --git a/OpenRA.Editor/OpenRA.Editor.csproj b/OpenRA.Editor/OpenRA.Editor.csproj index d4ddf115d0..8c6687a4a5 100644 --- a/OpenRA.Editor/OpenRA.Editor.csproj +++ b/OpenRA.Editor/OpenRA.Editor.csproj @@ -160,6 +160,10 @@ {0DFB103F-2962-400F-8C6D-E2C28CCBA633} OpenRA.Game + + {FE6C8CC0-2F07-442A-B29F-17617B3B7FC6} + OpenRA.Mods.Common + diff --git a/OpenRA.Editor/RenderUtils.cs b/OpenRA.Editor/RenderUtils.cs index a3497a882f..ca8bb84eb7 100644 --- a/OpenRA.Editor/RenderUtils.cs +++ b/OpenRA.Editor/RenderUtils.cs @@ -14,13 +14,14 @@ using System.Linq; using OpenRA.FileFormats; using OpenRA.FileSystem; using OpenRA.Graphics; +using OpenRA.Mods.Common.SpriteLoaders; using OpenRA.Traits; namespace OpenRA.Editor { static class RenderUtils { - static Bitmap RenderShp(ISpriteSource shp, IPalette p) + static Bitmap RenderShp(ShpTDSprite shp, IPalette p) { var frame = shp.Frames.First(); @@ -50,14 +51,14 @@ namespace OpenRA.Editor var image = info.Traits.Get().EditorImage(info); using (var s = GlobalFileSystem.OpenWithExts(image, tileset.Extensions)) { - var shp = new ShpReader(s); + var shp = new ShpTDSprite(s); var bitmap = RenderShp(shp, p); try { using (var s2 = GlobalFileSystem.OpenWithExts(image + "2", tileset.Extensions)) { - var shp2 = new ShpReader(s2); + var shp2 = new ShpTDSprite(s2); var roofBitmap = RenderShp(shp2, p); using (var g = System.Drawing.Graphics.FromImage(bitmap)) @@ -81,7 +82,7 @@ namespace OpenRA.Editor using (var s = GlobalFileSystem.OpenWithExts(image, exts)) { // TODO: Do this properly - var shp = new ShpReader(s) as ISpriteSource; + var shp = new ShpTDSprite(s); var frame = shp.Frames.Last(); var bitmap = new Bitmap(frame.Size.Width, frame.Size.Height, PixelFormat.Format8bppIndexed); diff --git a/OpenRA.Editor/Surface.cs b/OpenRA.Editor/Surface.cs index 20b317ee2b..e801a283aa 100644 --- a/OpenRA.Editor/Surface.cs +++ b/OpenRA.Editor/Surface.cs @@ -271,18 +271,19 @@ namespace OpenRA.Editor for (var i = 0; i < ChunkSize; i++) for (var j = 0; j < ChunkSize; j++) { - var cell = new CPos(u * ChunkSize + i, v * ChunkSize + j); - var tr = Map.MapTiles.Value[cell]; + var ui = u * ChunkSize + i; + var vj = v * ChunkSize + j; + var tr = Map.MapTiles.Value[ui, vj]; var tile = TileSetRenderer.Data(tr.Type); - var index = (tr.Index < tile.Count) ? tr.Index : (byte)0; + var index = (tr.Index < tile.Length) ? tr.Index : (byte)0; var rawImage = tile[index]; for (var x = 0; x < TileSetRenderer.TileSize; x++) for (var y = 0; y < TileSetRenderer.TileSize; y++) p[(j * TileSetRenderer.TileSize + y) * stride + i * TileSetRenderer.TileSize + x] = Palette.GetColor(rawImage[x + TileSetRenderer.TileSize * y]).ToArgb(); - if (Map.MapResources.Value[cell].Type != 0) + if (Map.MapResources.Value[ui, vj].Type != 0) { - var resourceImage = ResourceTemplates[Map.MapResources.Value[cell].Type].Bitmap; + var resourceImage = ResourceTemplates[Map.MapResources.Value[ui, vj].Type].Bitmap; var srcdata = resourceImage.LockBits(resourceImage.Bounds(), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); diff --git a/OpenRA.Editor/TileSetRenderer.cs b/OpenRA.Editor/TileSetRenderer.cs index c1bcef7c46..c39b9b5cf4 100644 --- a/OpenRA.Editor/TileSetRenderer.cs +++ b/OpenRA.Editor/TileSetRenderer.cs @@ -22,7 +22,7 @@ namespace OpenRA.Editor { public readonly int TileSize; public TileSet TileSet; - Dictionary> templates; + Dictionary templates; // Extract a square tile that the editor can render byte[] ExtractSquareTile(ISpriteFrame frame) @@ -44,42 +44,19 @@ namespace OpenRA.Editor return data; } - List LoadTemplate(string filename, string[] exts, Dictionary sourceCache, int[] frames) - { - ISpriteSource source; - if (!sourceCache.ContainsKey(filename)) - { - using (var s = GlobalFileSystem.OpenWithExts(filename, exts)) - source = SpriteSource.LoadSpriteSource(s, filename); - - if (source.CacheWhenLoadingTileset) - sourceCache.Add(filename, source); - } - else - source = sourceCache[filename]; - - if (frames != null) - { - var ret = new List(); - var srcFrames = source.Frames; - foreach (var i in frames) - ret.Add(ExtractSquareTile(srcFrames[i])); - - return ret; - } - - return source.Frames.Select(f => ExtractSquareTile(f)).ToList(); - } - public TileSetRenderer(TileSet tileset, Size tileSize) { this.TileSet = tileset; this.TileSize = Math.Min(tileSize.Width, tileSize.Height); - templates = new Dictionary>(); - var sourceCache = new Dictionary(); - foreach (var t in TileSet.Templates) - templates.Add(t.Key, LoadTemplate(t.Value.Image, tileset.Extensions, sourceCache, t.Value.Frames)); + templates = new Dictionary(); + var spriteLoader = new SpriteLoader(Game.modData.SpriteLoaders, tileset.Extensions, null); + foreach (var t in tileset.Templates) + { + var allFrames = spriteLoader.LoadAllFrames(t.Value.Image); + var frames = t.Value.Frames != null ? t.Value.Frames.Select(f => allFrames[f]).ToArray() : allFrames; + templates.Add(t.Value.Id, frames.Select(f => ExtractSquareTile(f)).ToArray()); + } } public Bitmap RenderTemplate(ushort id, IPalette p) @@ -125,7 +102,7 @@ namespace OpenRA.Editor return bitmap; } - public List Data(ushort id) + public byte[][] Data(ushort id) { return templates[id]; } diff --git a/OpenRA.Game/FileFormats/TmpRAReader.cs b/OpenRA.Game/FileFormats/TmpRAReader.cs deleted file mode 100644 index 5b8f4b520e..0000000000 --- a/OpenRA.Game/FileFormats/TmpRAReader.cs +++ /dev/null @@ -1,52 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation. For more information, - * see COPYING. - */ -#endregion - -using System.Drawing; -using System.IO; -using OpenRA.Graphics; - -namespace OpenRA.FileFormats -{ - public class TmpRAReader : ISpriteSource - { - public IReadOnlyList Frames { get; private set; } - public bool CacheWhenLoadingTileset { get { return false; } } - - public TmpRAReader(Stream s) - { - var width = s.ReadUInt16(); - var height = s.ReadUInt16(); - var size = new Size(width, height); - - s.Position += 12; - var imgStart = s.ReadUInt32(); - s.Position += 8; - var indexEnd = s.ReadInt32(); - s.Position += 4; - var indexStart = s.ReadInt32(); - - s.Position = indexStart; - var count = indexEnd - indexStart; - var tiles = new TmpTile[count]; - Frames = tiles.AsReadOnly(); - var tilesIndex = 0; - foreach (var b in s.ReadBytes(count)) - { - if (b != 255) - { - s.Position = imgStart + b * width * height; - tiles[tilesIndex++] = new TmpTile(s.ReadBytes(width * height), size); - } - else - tiles[tilesIndex++] = new TmpTile(null, size); - } - } - } -} diff --git a/OpenRA.Game/FileFormats/TmpTDReader.cs b/OpenRA.Game/FileFormats/TmpTDReader.cs deleted file mode 100644 index d0521460d2..0000000000 --- a/OpenRA.Game/FileFormats/TmpTDReader.cs +++ /dev/null @@ -1,70 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation. For more information, - * see COPYING. - */ -#endregion - -using System.Drawing; -using System.IO; -using OpenRA.Graphics; - -namespace OpenRA.FileFormats -{ - public class TmpTile : ISpriteFrame - { - public Size Size { get; private set; } - public Size FrameSize { get; private set; } - public float2 Offset { get { return float2.Zero; } } - public byte[] Data { get; set; } - - public TmpTile(byte[] data, Size size) - { - FrameSize = size; - Data = data; - - if (data == null) - Data = new byte[0]; - else - Size = size; - } - } - - public class TmpTDReader : ISpriteSource - { - public IReadOnlyList Frames { get; private set; } - public bool CacheWhenLoadingTileset { get { return false; } } - - public TmpTDReader(Stream s) - { - var width = s.ReadUInt16(); - var height = s.ReadUInt16(); - var size = new Size(width, height); - - s.Position += 8; - var imgStart = s.ReadUInt32(); - s.Position += 8; - var indexEnd = s.ReadInt32(); - var indexStart = s.ReadInt32(); - - s.Position = indexStart; - var count = indexEnd - indexStart; - var tiles = new TmpTile[count]; - Frames = tiles.AsReadOnly(); - var tilesIndex = 0; - foreach (var b in s.ReadBytes(count)) - { - if (b != 255) - { - s.Position = imgStart + b * width * height; - tiles[tilesIndex++] = new TmpTile(s.ReadBytes(width * height), size); - } - else - tiles[tilesIndex++] = new TmpTile(null, size); - } - } - } -} diff --git a/OpenRA.Game/FileFormats/TmpTSReader.cs b/OpenRA.Game/FileFormats/TmpTSReader.cs deleted file mode 100644 index d405e2d01d..0000000000 --- a/OpenRA.Game/FileFormats/TmpTSReader.cs +++ /dev/null @@ -1,76 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation. For more information, - * see COPYING. - */ -#endregion - -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using OpenRA.Graphics; - -namespace OpenRA.FileFormats -{ - public class TmpTSTile : ISpriteFrame - { - public Size Size { get; private set; } - public Size FrameSize { get { return Size; } } - public float2 Offset { get { return float2.Zero; } } - public byte[] Data { get; set; } - - public TmpTSTile(Stream s, Size size) - { - Size = size; - - // Ignore tile header for now - s.Position += 52; - - Data = new byte[size.Width * size.Height]; - - // Unpack tile data - var width = 4; - for (var i = 0; i < size.Height; i++) - { - var start = i * size.Width + (size.Width - width) / 2; - for (var j = 0; j < width; j++) - Data[start + j] = s.ReadUInt8(); - - width += (i < size.Height / 2 - 1 ? 1 : -1) * 4; - } - - // Ignore Z-data for now - // Ignore extra data for now - } - } - - public class TmpTSReader : ISpriteSource - { - public IReadOnlyList Frames { get; private set; } - public bool CacheWhenLoadingTileset { get { return false; } } - - public TmpTSReader(Stream s) - { - var templateWidth = s.ReadUInt32(); - var templateHeight = s.ReadUInt32(); - var tileWidth = s.ReadInt32(); - var tileHeight = s.ReadInt32(); - var size = new Size(tileWidth, tileHeight); - var offsets = new uint[templateWidth * templateHeight]; - for (var i = 0; i < offsets.Length; i++) - offsets[i] = s.ReadUInt32(); - - var tiles = new List(); - for (var i = 0; i < offsets.Length; i++) - { - s.Position = offsets[i]; - tiles.Add(new TmpTSTile(s, size)); - } - - Frames = tiles.ToArray().AsReadOnly(); - } - } -} diff --git a/OpenRA.Game/Graphics/CursorProvider.cs b/OpenRA.Game/Graphics/CursorProvider.cs index 042e3164d2..28701e2fd4 100644 --- a/OpenRA.Game/Graphics/CursorProvider.cs +++ b/OpenRA.Game/Graphics/CursorProvider.cs @@ -45,7 +45,7 @@ namespace OpenRA.Graphics foreach (var p in nodesDict["Palettes"].Nodes) palette.AddPalette(p.Key, new ImmutablePalette(GlobalFileSystem.Open(p.Value.Value), shadowIndex), false); - var spriteLoader = new SpriteLoader(new string[0], new SheetBuilder(SheetType.Indexed)); + var spriteLoader = new SpriteLoader(modData.SpriteLoaders, new string[0], new SheetBuilder(SheetType.Indexed)); foreach (var s in nodesDict["Cursors"].Nodes) LoadSequencesForCursor(spriteLoader, s.Key, s.Value); spriteLoader.SheetBuilder.Current.ReleaseBuffer(); diff --git a/OpenRA.Game/Graphics/SequenceProvider.cs b/OpenRA.Game/Graphics/SequenceProvider.cs index 2f3947e137..bed18baac0 100644 --- a/OpenRA.Game/Graphics/SequenceProvider.cs +++ b/OpenRA.Game/Graphics/SequenceProvider.cs @@ -79,7 +79,7 @@ namespace OpenRA.Graphics { this.modData = modData; - spriteLoader = Exts.Lazy(() => new SpriteLoader(tileSet.Extensions, new SheetBuilder(SheetType.Indexed))); + spriteLoader = Exts.Lazy(() => new SpriteLoader(modData.SpriteLoaders, tileSet.Extensions, new SheetBuilder(SheetType.Indexed))); } public Sequences LoadSequences(Map map) diff --git a/OpenRA.Game/Graphics/SpriteLoader.cs b/OpenRA.Game/Graphics/SpriteLoader.cs index fe7c8781b5..d542a1ea89 100644 --- a/OpenRA.Game/Graphics/SpriteLoader.cs +++ b/OpenRA.Game/Graphics/SpriteLoader.cs @@ -8,35 +8,67 @@ */ #endregion +using System.Drawing; +using System.IO; using System.Linq; using OpenRA.FileSystem; using OpenRA.Primitives; namespace OpenRA.Graphics { + public interface ISpriteLoader + { + bool TryParseSprite(Stream s, out ISpriteFrame[] frames); + } + + public interface ISpriteFrame + { + Size Size { get; } + Size FrameSize { get; } + float2 Offset { get; } + byte[] Data { get; } + bool DisableExportPadding { get; } + } + public class SpriteLoader { public readonly SheetBuilder SheetBuilder; + readonly ISpriteLoader[] loaders; readonly Cache sprites; + readonly Cache frames; readonly string[] exts; - public SpriteLoader(string[] exts, SheetBuilder sheetBuilder) + public SpriteLoader(ISpriteLoader[] loaders, string[] exts, SheetBuilder sheetBuilder) { + this.loaders = loaders; SheetBuilder = sheetBuilder; // Include extension-less version this.exts = exts.Append("").ToArray(); - sprites = new Cache(CacheSpriteFrames); + sprites = new Cache(CacheSprites); + frames = new Cache(CacheFrames); } - Sprite[] CacheSpriteFrames(string filename) + Sprite[] CacheSprites(string filename) + { + return frames[filename].Select(a => SheetBuilder.Add(a)) + .ToArray(); + } + + ISpriteFrame[] CacheFrames(string filename) { using (var stream = GlobalFileSystem.OpenWithExts(filename, exts)) - return SpriteSource.LoadSpriteSource(stream, filename).Frames - .Select(a => SheetBuilder.Add(a)) - .ToArray(); + { + ISpriteFrame[] frames; + foreach (var loader in loaders) + if (loader.TryParseSprite(stream, out frames)) + return frames; + + throw new InvalidDataException(filename + " is not a valid sprite file"); + } } public Sprite[] LoadAllSprites(string filename) { return sprites[filename]; } + public ISpriteFrame[] LoadAllFrames(string filename) { return frames[filename]; } } } diff --git a/OpenRA.Game/Graphics/SpriteSource.cs b/OpenRA.Game/Graphics/SpriteSource.cs deleted file mode 100644 index ae23aa5501..0000000000 --- a/OpenRA.Game/Graphics/SpriteSource.cs +++ /dev/null @@ -1,264 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation. For more information, - * see COPYING. - */ -#endregion - -using System.Drawing; -using System.IO; -using OpenRA.FileFormats; - -namespace OpenRA.Graphics -{ - public interface ISpriteFrame - { - Size Size { get; } - Size FrameSize { get; } - float2 Offset { get; } - byte[] Data { get; } - } - - public interface ISpriteSource - { - IReadOnlyList Frames { get; } - bool CacheWhenLoadingTileset { get; } - } - - // TODO: Most of this should be moved into the format parsers themselves. - public enum SpriteType { Unknown, ShpTD, ShpTS, ShpD2, TmpTD, TmpRA, TmpTS, R8 } - public static class SpriteSource - { - static bool IsTmpRA(Stream s) - { - var start = s.Position; - - s.Position += 20; - var a = s.ReadUInt32(); - s.Position += 2; - var b = s.ReadUInt16(); - - s.Position = start; - return a == 0 && b == 0x2c73; - } - - static bool IsTmpTD(Stream s) - { - var start = s.Position; - - s.Position += 16; - var a = s.ReadUInt32(); - var b = s.ReadUInt32(); - - s.Position = start; - return a == 0 && b == 0x0D1AFFFF; - } - - static bool IsTmpTS(Stream s) - { - var start = s.Position; - s.Position += 8; - var sx = s.ReadUInt32(); - var sy = s.ReadUInt32(); - - // Find the first frame - var offset = s.ReadUInt32(); - - if (offset > s.Length - 52) - { - s.Position = start; - return false; - } - - s.Position = offset + 12; - var test = s.ReadUInt32(); - - s.Position = start; - return test == sx * sy / 2 + 52; - } - - static bool IsShpTS(Stream s) - { - var start = s.Position; - - // First word is zero - if (s.ReadUInt16() != 0) - { - s.Position = start; - return false; - } - - // Sanity Check the image count - s.Position += 4; - var imageCount = s.ReadUInt16(); - if (s.Position + 24 * imageCount > s.Length) - { - s.Position = start; - return false; - } - - // Check the size and format flag - // Some files define bogus frames, so loop until we find a valid one - s.Position += 4; - ushort w, h, f = 0; - byte type; - do - { - w = s.ReadUInt16(); - h = s.ReadUInt16(); - type = s.ReadUInt8(); - } - while (w == 0 && h == 0 && f++ < imageCount); - - s.Position = start; - return type < 4; - } - - static bool IsShpTD(Stream s) - { - var start = s.Position; - - // First word is the image count - var imageCount = s.ReadUInt16(); - if (imageCount == 0) - { - s.Position = start; - return false; - } - - // Last offset should point to the end of file - var finalOffset = start + 14 + 8 * imageCount; - if (finalOffset > s.Length) - { - s.Position = start; - return false; - } - - s.Position = finalOffset; - var eof = s.ReadUInt32(); - if (eof != s.Length) - { - s.Position = start; - return false; - } - - // Check the format flag on the first frame - s.Position = start + 17; - var b = s.ReadUInt8(); - - s.Position = start; - return b == 0x20 || b == 0x40 || b == 0x80; - } - - static bool IsShpD2(Stream s) - { - var start = s.Position; - - // First word is the image count - var imageCount = s.ReadUInt16(); - if (imageCount == 0) - { - s.Position = start; - return false; - } - - // Test for two vs four byte offset - var testOffset = s.ReadUInt32(); - var offsetSize = (testOffset & 0xFF0000) > 0 ? 2 : 4; - - // Last offset should point to the end of file - var finalOffset = start + 2 + offsetSize * imageCount; - if (finalOffset > s.Length) - { - s.Position = start; - return false; - } - - s.Position = finalOffset; - var eof = offsetSize == 2 ? s.ReadUInt16() : s.ReadUInt32(); - if (eof + 2 != s.Length) - { - s.Position = start; - return false; - } - - // Check the format flag on the first frame - var b = s.ReadUInt16(); - s.Position = start; - return b == 5 || b <= 3; - } - - static bool IsR8(Stream s) - { - var start = s.Position; - - // First byte is nonzero - if (s.ReadUInt8() == 0) - { - s.Position = start; - return false; - } - - // Check the format of the first frame - s.Position = start + 25; - var d = s.ReadUInt8(); - - s.Position = start; - return d == 8; - } - - public static SpriteType DetectSpriteType(Stream s) - { - if (IsShpTD(s)) - return SpriteType.ShpTD; - - if (IsShpTS(s)) - return SpriteType.ShpTS; - - if (IsR8(s)) - return SpriteType.R8; - - if (IsTmpRA(s)) - return SpriteType.TmpRA; - - if (IsTmpTD(s)) - return SpriteType.TmpTD; - - if (IsTmpTS(s)) - return SpriteType.TmpTS; - - if (IsShpD2(s)) - return SpriteType.ShpD2; - - return SpriteType.Unknown; - } - - public static ISpriteSource LoadSpriteSource(Stream s, string filename) - { - var type = DetectSpriteType(s); - switch (type) - { - case SpriteType.ShpTD: - return new ShpReader(s); - case SpriteType.ShpTS: - return new ShpTSReader(s); - case SpriteType.R8: - return new R8Reader(s); - case SpriteType.TmpRA: - return new TmpRAReader(s); - case SpriteType.TmpTD: - return new TmpTDReader(s); - case SpriteType.TmpTS: - return new TmpTSReader(s); - case SpriteType.ShpD2: - return new ShpD2Reader(s); - case SpriteType.Unknown: - default: - throw new InvalidDataException(filename + " is not a valid sprite file"); - } - } - } -} diff --git a/OpenRA.Game/Graphics/Theater.cs b/OpenRA.Game/Graphics/Theater.cs index e3b962160e..110b27703a 100644 --- a/OpenRA.Game/Graphics/Theater.cs +++ b/OpenRA.Game/Graphics/Theater.cs @@ -22,33 +22,6 @@ namespace OpenRA.Graphics Dictionary templates; Sprite missingTile; - Sprite[] LoadTemplate(string filename, string[] exts, Dictionary sourceCache, int[] frames) - { - ISpriteSource source; - if (!sourceCache.ContainsKey(filename)) - { - using (var s = GlobalFileSystem.OpenWithExts(filename, exts)) - source = SpriteSource.LoadSpriteSource(s, filename); - - if (source.CacheWhenLoadingTileset) - sourceCache.Add(filename, source); - } - else - source = sourceCache[filename]; - - if (frames != null) - { - var ret = new List(); - var srcFrames = source.Frames.ToArray(); - foreach (var i in frames) - ret.Add(sheetBuilder.Add(srcFrames[i])); - - return ret.ToArray(); - } - - return source.Frames.Select(f => sheetBuilder.Add(f)).ToArray(); - } - public Theater(TileSet tileset) { var allocated = false; @@ -61,11 +34,17 @@ namespace OpenRA.Graphics return new Sheet(new Size(tileset.SheetSize, tileset.SheetSize), true); }; - var sourceCache = new Dictionary(); - templates = new Dictionary(); sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate); + templates = new Dictionary(); + + // We manage the SheetBuilder ourselves, to avoid loading all of the tileset images + var spriteLoader = new SpriteLoader(Game.modData.SpriteLoaders, tileset.Extensions, null); foreach (var t in tileset.Templates) - templates.Add(t.Value.Id, LoadTemplate(t.Value.Image, tileset.Extensions, sourceCache, t.Value.Frames)); + { + var allFrames = spriteLoader.LoadAllFrames(t.Value.Image); + var frames = t.Value.Frames != null ? t.Value.Frames.Select(f => allFrames[f]).ToArray() : allFrames; + templates.Add(t.Value.Id, frames.Select(f => sheetBuilder.Add(f)).ToArray()); + } // 1x1px transparent tile missingTile = sheetBuilder.Add(new byte[1], new Size(1, 1)); diff --git a/OpenRA.Game/Manifest.cs b/OpenRA.Game/Manifest.cs index 282ae968d1..12fc57a47f 100644 --- a/OpenRA.Game/Manifest.cs +++ b/OpenRA.Game/Manifest.cs @@ -37,6 +37,8 @@ namespace OpenRA public readonly Size TileSize = new Size(24, 24); public readonly TileShape TileShape = TileShape.Rectangle; + public readonly string[] SpriteFormats = { }; + [Desc("(x,y,z) offset of the full cell and each sub-cell", "X & Y should be between -512 ... 512 and Z >= 0")] public readonly WVec[] SubCellOffsets = { @@ -128,6 +130,9 @@ namespace OpenRA compat.Add(c.Trim()); MapCompatibility = compat.ToArray(); + + if (yaml.ContainsKey("SpriteFormats")) + SpriteFormats = FieldLoader.GetValue("SpriteFormats", yaml["SpriteFormats"].Value); } static string[] YamlList(Dictionary yaml, string key) diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 311b4ffc02..30b8751024 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -457,7 +457,7 @@ namespace OpenRA for (var i = 0; i < MapSize.X; i++) for (var j = 0; j < MapSize.Y; j++) { - var tile = MapTiles.Value[new CPos(i, j)]; + var tile = MapTiles.Value[i, j]; writer.Write(tile.Type); writer.Write(tile.Index); } @@ -467,7 +467,7 @@ namespace OpenRA { for (var j = 0; j < MapSize.Y; j++) { - var tile = MapResources.Value[new CPos(i, j)]; + var tile = MapResources.Value[i, j]; writer.Write(tile.Type); writer.Write(tile.Index); } @@ -654,9 +654,8 @@ namespace OpenRA { for (var i = Bounds.Left; i < Bounds.Right; i++) { - var cell = new CPos(i, j); - var type = MapTiles.Value[cell].Type; - var index = MapTiles.Value[cell].Index; + var type = MapTiles.Value[i, j].Type; + var index = MapTiles.Value[i, j].Index; if (!tileset.Templates.ContainsKey(type)) { Console.WriteLine("Unknown Tile ID {0}".F(type)); @@ -668,7 +667,7 @@ namespace OpenRA continue; index = (byte)r.Next(0, template.TilesCount); - MapTiles.Value[cell] = new TerrainTile(type, index); + MapTiles.Value[i, j] = new TerrainTile(type, index); } } } @@ -730,7 +729,7 @@ namespace OpenRA for (var j = -max; j <= max; j++) for (var i = -max; i <= max; i++) if (max * max >= i * i + j * j) - ts [Exts.ISqrt(i * i + j * j, Exts.ISqrtRoundMode.Ceiling)].Add(new CVec(i, j)); + ts[Exts.ISqrt(i * i + j * j, Exts.ISqrtRoundMode.Ceiling)].Add(new CVec(i, j)); // Sort each integer-distance group by the actual distance foreach (var list in ts) diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index c5b5fc3a0c..288d9ac836 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -24,6 +24,7 @@ namespace OpenRA public readonly ObjectCreator ObjectCreator; public readonly WidgetLoader WidgetLoader; public readonly MapCache MapCache; + public readonly ISpriteLoader[] SpriteLoaders; public ILoadScreen LoadScreen = null; public VoxelLoader VoxelLoader; public readonly RulesetCache RulesetCache; @@ -45,6 +46,18 @@ namespace OpenRA RulesetCache.LoadingProgress += HandleLoadingProgress; MapCache = new MapCache(this); + var loaders = new List(); + foreach (var format in Manifest.SpriteFormats) + { + var loader = ObjectCreator.FindType(format + "Loader"); + if (loader == null || !loader.GetInterfaces().Contains(typeof(ISpriteLoader))) + throw new InvalidOperationException("Unable to find a sprite loader for type '{0}'.".F(format)); + + loaders.Add((ISpriteLoader)ObjectCreator.CreateBasic(loader)); + } + + SpriteLoaders = loaders.ToArray(); + // HACK: Mount only local folders so we have a half-working environment for the asset installer GlobalFileSystem.UnmountAll(); foreach (var dir in Manifest.Folders) diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index e675c54864..6eb94aca68 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -277,13 +277,6 @@ - - - - - - - @@ -320,7 +313,6 @@ - diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 25a741cc5d..68c487a53d 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -87,7 +87,6 @@ - @@ -114,6 +113,11 @@ + + + + + diff --git a/OpenRA.Game/FileFormats/ShpD2Reader.cs b/OpenRA.Mods.Common/SpriteLoaders/ShpD2Loader.cs similarity index 61% rename from OpenRA.Game/FileFormats/ShpD2Reader.cs rename to OpenRA.Mods.Common/SpriteLoaders/ShpD2Loader.cs index dca7a888d6..c5567cefec 100644 --- a/OpenRA.Game/FileFormats/ShpD2Reader.cs +++ b/OpenRA.Mods.Common/SpriteLoaders/ShpD2Loader.cs @@ -9,13 +9,16 @@ #endregion using System; +using System.Collections.Generic; using System.Drawing; using System.IO; +using System.Linq; +using OpenRA.FileFormats; using OpenRA.Graphics; -namespace OpenRA.FileFormats +namespace OpenRA.Mods.Common.SpriteLoaders { - public class ShpD2Reader : ISpriteSource + public class ShpD2Loader : ISpriteLoader { [Flags] enum FormatFlags : int { @@ -24,14 +27,15 @@ namespace OpenRA.FileFormats VariableLengthTable = 4 } - class Frame : ISpriteFrame + class ShpD2Frame : ISpriteFrame { public Size Size { get; private set; } public Size FrameSize { get { return Size; } } public float2 Offset { get { return float2.Zero; } } public byte[] Data { get; set; } + public bool DisableExportPadding { get { return false; } } - public Frame(Stream s) + public ShpD2Frame(Stream s) { var flags = (FormatFlags)s.ReadUInt16(); s.Position += 1; @@ -83,11 +87,48 @@ namespace OpenRA.FileFormats } } - public IReadOnlyList Frames { get; private set; } - public bool CacheWhenLoadingTileset { get { return false; } } - - public ShpD2Reader(Stream s) + bool IsShpD2(Stream s) { + var start = s.Position; + + // First word is the image count + var imageCount = s.ReadUInt16(); + if (imageCount == 0) + { + s.Position = start; + return false; + } + + // Test for two vs four byte offset + var testOffset = s.ReadUInt32(); + var offsetSize = (testOffset & 0xFF0000) > 0 ? 2 : 4; + + // Last offset should point to the end of file + var finalOffset = start + 2 + offsetSize * imageCount; + if (finalOffset > s.Length) + { + s.Position = start; + return false; + } + + s.Position = finalOffset; + var eof = offsetSize == 2 ? s.ReadUInt16() : s.ReadUInt32(); + if (eof + 2 != s.Length) + { + s.Position = start; + return false; + } + + // Check the format flag on the first frame + var b = s.ReadUInt16(); + s.Position = start; + return b == 5 || b <= 3; + } + + ShpD2Frame[] ParseFrames(Stream s) + { + var start = s.Position; + var imageCount = s.ReadUInt16(); // Last offset is pointer to end of file. @@ -101,13 +142,27 @@ namespace OpenRA.FileFormats for (var i = 0; i < imageCount + 1; i++) offsets[i] = (twoByteOffset ? s.ReadUInt16() : s.ReadUInt32()) + 2; - var frames = new Frame[imageCount]; - Frames = frames.AsReadOnly(); + var frames = new ShpD2Frame[imageCount]; for (var i = 0; i < frames.Length; i++) { s.Position = offsets[i]; - frames[i] = new Frame(s); + frames[i] = new ShpD2Frame(s); } + + s.Position = start; + return frames; + } + + public bool TryParseSprite(Stream s, out ISpriteFrame[] frames) + { + if (!IsShpD2(s)) + { + frames = null; + return false; + } + + frames = ParseFrames(s); + return true; } } } diff --git a/OpenRA.Game/FileFormats/ShpReader.cs b/OpenRA.Mods.Common/SpriteLoaders/ShpTDLoader.cs similarity index 79% rename from OpenRA.Game/FileFormats/ShpReader.cs rename to OpenRA.Mods.Common/SpriteLoaders/ShpTDLoader.cs index 0d7004cca5..055375889a 100644 --- a/OpenRA.Game/FileFormats/ShpReader.cs +++ b/OpenRA.Mods.Common/SpriteLoaders/ShpTDLoader.cs @@ -13,11 +13,63 @@ using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; +using OpenRA.FileFormats; using OpenRA.Graphics; -namespace OpenRA.FileFormats +namespace OpenRA.Mods.Common.SpriteLoaders { - public class ShpReader : ISpriteSource + public class ShpTDLoader : ISpriteLoader + { + static bool IsShpTD(Stream s) + { + var start = s.Position; + + // First word is the image count + var imageCount = s.ReadUInt16(); + if (imageCount == 0) + { + s.Position = start; + return false; + } + + // Last offset should point to the end of file + var finalOffset = start + 14 + 8 * imageCount; + if (finalOffset > s.Length) + { + s.Position = start; + return false; + } + + s.Position = finalOffset; + var eof = s.ReadUInt32(); + if (eof != s.Length) + { + s.Position = start; + return false; + } + + // Check the format flag on the first frame + s.Position = start + 17; + var b = s.ReadUInt8(); + + s.Position = start; + return b == 0x20 || b == 0x40 || b == 0x80; + } + + public bool TryParseSprite(Stream s, out ISpriteFrame[] frames) + { + if (!IsShpTD(s)) + { + frames = null; + return false; + } + + frames = new ShpTDSprite(s).Frames.ToArray(); + return true; + } + } + + public class ShpTDSprite { enum Format { Format20 = 0x20, Format40 = 0x40, Format80 = 0x80 } @@ -27,6 +79,7 @@ namespace OpenRA.FileFormats public Size FrameSize { get { return reader.Size; } } public float2 Offset { get { return float2.Zero; } } public byte[] Data { get; set; } + public bool DisableExportPadding { get { return false; } } public uint FileOffset; public Format Format; @@ -35,12 +88,12 @@ namespace OpenRA.FileFormats public Format RefFormat; public ImageHeader RefImage; - ShpReader reader; + ShpTDSprite reader; // Used by ShpWriter public ImageHeader() { } - public ImageHeader(Stream stream, ShpReader reader) + public ImageHeader(Stream stream, ShpTDSprite reader) { this.reader = reader; var data = stream.ReadUInt32(); @@ -60,7 +113,6 @@ namespace OpenRA.FileFormats } public IReadOnlyList Frames { get; private set; } - public bool CacheWhenLoadingTileset { get { return false; } } public readonly Size Size; int recurseDepth = 0; @@ -69,7 +121,7 @@ namespace OpenRA.FileFormats readonly long shpBytesFileOffset; readonly byte[] shpBytes; - public ShpReader(Stream stream) + public ShpTDSprite(Stream stream) { imageCount = stream.ReadUInt16(); stream.Position += 4; @@ -149,12 +201,6 @@ namespace OpenRA.FileFormats return imageData; } - public static ShpReader Load(string filename) - { - using (var s = File.OpenRead(filename)) - return new ShpReader(s); - } - public static void Write(Stream s, Size size, IEnumerable frames) { var compressedFrames = frames.Select(f => Format80.Encode(f)).ToArray(); diff --git a/OpenRA.Game/FileFormats/ShpTSReader.cs b/OpenRA.Mods.Common/SpriteLoaders/ShpTSLoader.cs similarity index 51% rename from OpenRA.Game/FileFormats/ShpTSReader.cs rename to OpenRA.Mods.Common/SpriteLoaders/ShpTSLoader.cs index 661a2790f4..6471be0aab 100644 --- a/OpenRA.Game/FileFormats/ShpTSReader.cs +++ b/OpenRA.Mods.Common/SpriteLoaders/ShpTSLoader.cs @@ -8,25 +8,30 @@ */ #endregion +using System; +using System.Collections.Generic; using System.Drawing; using System.IO; +using System.Linq; +using OpenRA.FileFormats; using OpenRA.Graphics; -namespace OpenRA.FileFormats +namespace OpenRA.Mods.Common.SpriteLoaders { - public class ShpTSReader : ISpriteSource + public class ShpTSLoader : ISpriteLoader { - class FrameHeader : ISpriteFrame + class ShpTSFrame : ISpriteFrame { public Size Size { get; private set; } public Size FrameSize { get; private set; } public float2 Offset { get; private set; } public byte[] Data { get; set; } + public bool DisableExportPadding { get { return false; } } public readonly uint FileOffset; public readonly byte Format; - public FrameHeader(Stream stream, Size frameSize) + public ShpTSFrame(Stream stream, Size frameSize) { var x = stream.ReadUInt16(); var y = stream.ReadUInt16(); @@ -44,21 +49,56 @@ namespace OpenRA.FileFormats } } - public IReadOnlyList Frames { get; private set; } - public bool CacheWhenLoadingTileset { get { return false; } } - - public ShpTSReader(Stream stream) + bool IsShpTS(Stream s) { - stream.ReadUInt16(); - var width = stream.ReadUInt16(); - var height = stream.ReadUInt16(); - var size = new Size(width, height); - var frameCount = stream.ReadUInt16(); + var start = s.Position; - var frames = new FrameHeader[frameCount]; - Frames = frames.AsReadOnly(); + // First word is zero + if (s.ReadUInt16() != 0) + { + s.Position = start; + return false; + } + + // Sanity Check the image count + s.Position += 4; + var imageCount = s.ReadUInt16(); + if (s.Position + 24 * imageCount > s.Length) + { + s.Position = start; + return false; + } + + // Check the size and format flag + // Some files define bogus frames, so loop until we find a valid one + s.Position += 4; + ushort w, h, f = 0; + byte type; + do + { + w = s.ReadUInt16(); + h = s.ReadUInt16(); + type = s.ReadUInt8(); + } + while (w == 0 && h == 0 && f++ < imageCount); + + s.Position = start; + return type < 4; + } + + ShpTSFrame[] ParseFrames(Stream s) + { + var start = s.Position; + + s.ReadUInt16(); + var width = s.ReadUInt16(); + var height = s.ReadUInt16(); + var size = new Size(width, height); + var frameCount = s.ReadUInt16(); + + var frames = new ShpTSFrame[frameCount]; for (var i = 0; i < frames.Length; i++) - frames[i] = new FrameHeader(stream, size); + frames[i] = new ShpTSFrame(s, size); for (var i = 0; i < frameCount; i++) { @@ -66,13 +106,13 @@ namespace OpenRA.FileFormats if (f.FileOffset == 0) continue; - stream.Position = f.FileOffset; + s.Position = f.FileOffset; var frameSize = f.Size.Width * f.Size.Height; // Uncompressed if (f.Format == 1 || f.Format == 0) - f.Data = stream.ReadBytes(frameSize); + f.Data = s.ReadBytes(frameSize); // Uncompressed scanlines else if (f.Format == 2) @@ -80,9 +120,9 @@ namespace OpenRA.FileFormats f.Data = new byte[frameSize]; for (var j = 0; j < f.Size.Height; j++) { - var length = stream.ReadUInt16() - 2; + var length = s.ReadUInt16() - 2; var offset = f.Size.Width * j; - stream.ReadBytes(f.Data, offset, length); + s.ReadBytes(f.Data, offset, length); } } @@ -92,12 +132,27 @@ namespace OpenRA.FileFormats f.Data = new byte[frameSize]; for (var j = 0; j < f.Size.Height; j++) { - var length = stream.ReadUInt16() - 2; + var length = s.ReadUInt16() - 2; var offset = f.Size.Width * j; - Format2.DecodeInto(stream.ReadBytes(length), f.Data, offset); + Format2.DecodeInto(s.ReadBytes(length), f.Data, offset); } } } + + s.Position = start; + return frames; + } + + public bool TryParseSprite(Stream s, out ISpriteFrame[] frames) + { + if (!IsShpTS(s)) + { + frames = null; + return false; + } + + frames = ParseFrames(s); + return true; } } -} \ No newline at end of file +} diff --git a/OpenRA.Mods.Common/SpriteLoaders/TmpRALoader.cs b/OpenRA.Mods.Common/SpriteLoaders/TmpRALoader.cs new file mode 100644 index 0000000000..900c3fdd0c --- /dev/null +++ b/OpenRA.Mods.Common/SpriteLoaders/TmpRALoader.cs @@ -0,0 +1,102 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; + +namespace OpenRA.Mods.Common.SpriteLoaders +{ + public class TmpRALoader : ISpriteLoader + { + class TmpRAFrame : ISpriteFrame + { + public Size Size { get; private set; } + public Size FrameSize { get; private set; } + public float2 Offset { get { return float2.Zero; } } + public byte[] Data { get; set; } + public bool DisableExportPadding { get { return false; } } + + public TmpRAFrame(byte[] data, Size size) + { + FrameSize = size; + Data = data; + + if (data == null) + Data = new byte[0]; + else + Size = size; + } + } + + bool IsTmpRA(Stream s) + { + var start = s.Position; + + s.Position += 20; + var a = s.ReadUInt32(); + s.Position += 2; + var b = s.ReadUInt16(); + + s.Position = start; + return a == 0 && b == 0x2c73; + } + + TmpRAFrame[] ParseFrames(Stream s) + { + var start = s.Position; + var width = s.ReadUInt16(); + var height = s.ReadUInt16(); + var size = new Size(width, height); + + s.Position += 12; + var imgStart = s.ReadUInt32(); + s.Position += 8; + var indexEnd = s.ReadInt32(); + s.Position += 4; + var indexStart = s.ReadInt32(); + + s.Position = indexStart; + var count = indexEnd - indexStart; + var tiles = new TmpRAFrame[count]; + + var tilesIndex = 0; + foreach (var b in s.ReadBytes(count)) + { + if (b != 255) + { + s.Position = imgStart + b * width * height; + tiles[tilesIndex++] = new TmpRAFrame(s.ReadBytes(width * height), size); + } + else + tiles[tilesIndex++] = new TmpRAFrame(null, size); + } + + s.Position = start; + return tiles; + } + + public bool TryParseSprite(Stream s, out ISpriteFrame[] frames) + { + if (!IsTmpRA(s)) + { + frames = null; + return false; + } + + frames = ParseFrames(s); + return true; + } + } +} diff --git a/OpenRA.Mods.Common/SpriteLoaders/TmpTDLoader.cs b/OpenRA.Mods.Common/SpriteLoaders/TmpTDLoader.cs new file mode 100644 index 0000000000..22617faecf --- /dev/null +++ b/OpenRA.Mods.Common/SpriteLoaders/TmpTDLoader.cs @@ -0,0 +1,99 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; + +namespace OpenRA.Mods.Common.SpriteLoaders +{ + public class TmpTDLoader : ISpriteLoader + { + class TmpTDFrame : ISpriteFrame + { + public Size Size { get; private set; } + public Size FrameSize { get; private set; } + public float2 Offset { get { return float2.Zero; } } + public byte[] Data { get; set; } + public bool DisableExportPadding { get { return false; } } + + public TmpTDFrame(byte[] data, Size size) + { + FrameSize = size; + Data = data; + + if (data == null) + Data = new byte[0]; + else + Size = size; + } + } + + bool IsTmpTD(Stream s) + { + var start = s.Position; + + s.Position += 16; + var a = s.ReadUInt32(); + var b = s.ReadUInt32(); + + s.Position = start; + return a == 0 && b == 0x0D1AFFFF; + } + + TmpTDFrame[] ParseFrames(Stream s) + { + var start = s.Position; + var width = s.ReadUInt16(); + var height = s.ReadUInt16(); + var size = new Size(width, height); + + s.Position += 8; + var imgStart = s.ReadUInt32(); + s.Position += 8; + var indexEnd = s.ReadInt32(); + var indexStart = s.ReadInt32(); + + s.Position = indexStart; + var count = indexEnd - indexStart; + var tiles = new TmpTDFrame[count]; + var tilesIndex = 0; + foreach (var b in s.ReadBytes(count)) + { + if (b != 255) + { + s.Position = imgStart + b * width * height; + tiles[tilesIndex++] = new TmpTDFrame(s.ReadBytes(width * height), size); + } + else + tiles[tilesIndex++] = new TmpTDFrame(null, size); + } + + s.Position = start; + return tiles; + } + + public bool TryParseSprite(Stream s, out ISpriteFrame[] frames) + { + if (!IsTmpTD(s)) + { + frames = null; + return false; + } + + frames = ParseFrames(s); + return true; + } + } +} diff --git a/OpenRA.Mods.Common/UtilityCommands/ConvertPngToShpCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ConvertPngToShpCommand.cs index 4cef69cec1..d52b7fee69 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ConvertPngToShpCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ConvertPngToShpCommand.cs @@ -17,6 +17,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using OpenRA.FileFormats; +using OpenRA.Mods.Common.SpriteLoaders; namespace OpenRA.Mods.Common.UtilityCommands { @@ -36,7 +37,7 @@ namespace OpenRA.Mods.Common.UtilityCommands throw new InvalidOperationException("All frames must be the same size"); using (var destStream = File.Create(dest)) - ShpReader.Write(destStream, size, frames.Select(f => ToBytes(f))); + ShpTDSprite.Write(destStream, size, frames.Select(f => ToBytes(f))); Console.WriteLine(dest + " saved."); } diff --git a/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs index 16fac878aa..f70338b257 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs @@ -41,19 +41,17 @@ namespace OpenRA.Mods.Common.UtilityCommands var palette = new ImmutablePalette(args[2], shadowIndex); - ISpriteSource source; - using (var stream = File.OpenRead(src)) - source = SpriteSource.LoadSpriteSource(stream, src); + var frames = new SpriteLoader(modData.SpriteLoaders, new string[0], null) + .LoadAllFrames(src); - // The r8 padding requires external information that we can't access here. - var usePadding = !(args.Contains("--nopadding") || source is R8Reader); + var usePadding = !args.Contains("--nopadding"); var count = 0; var prefix = Path.GetFileNameWithoutExtension(src); - foreach (var frame in source.Frames) + foreach (var frame in frames) { - var frameSize = usePadding ? frame.FrameSize : frame.Size; - var offset = usePadding ? (frame.Offset - 0.5f * new float2(frame.Size - frame.FrameSize)).ToInt2() : int2.Zero; + var frameSize = usePadding && !frame.DisableExportPadding ? frame.FrameSize : frame.Size; + var offset = usePadding && !frame.DisableExportPadding ? (frame.Offset - 0.5f * new float2(frame.Size - frame.FrameSize)).ToInt2() : int2.Zero; // shp(ts) may define empty frames if (frameSize.Width == 0 && frameSize.Height == 0) @@ -69,7 +67,7 @@ namespace OpenRA.Mods.Common.UtilityCommands ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); // Clear the frame - if (usePadding) + if (usePadding && !frame.DisableExportPadding) { var clearRow = new byte[data.Stride]; for (var i = 0; i < frameSize.Height; i++) diff --git a/OpenRA.Mods.Common/UtilityCommands/RemapShpCommand.cs b/OpenRA.Mods.Common/UtilityCommands/RemapShpCommand.cs index 59ee1885b6..1bffa57ff2 100644 --- a/OpenRA.Mods.Common/UtilityCommands/RemapShpCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/RemapShpCommand.cs @@ -18,6 +18,7 @@ using OpenRA.Traits; using OpenRA.Graphics; using OpenRA.FileFormats; using OpenRA.FileSystem; +using OpenRA.Mods.Common.SpriteLoaders; namespace OpenRA.Mods.Common.UtilityCommands { @@ -65,11 +66,13 @@ namespace OpenRA.Mods.Common.UtilityCommands .Where(a => !remap.ContainsValue(a)) .MinBy(a => ColorDistance(destPalette[a], srcPalette[i])); - var srcImage = ShpReader.Load(args[3]); - + using (var s = File.OpenRead(args[3])) using (var destStream = File.Create(args[4])) - ShpReader.Write(destStream, srcImage.Size, + { + var srcImage = new ShpTDSprite(s); + ShpTDSprite.Write(destStream, srcImage.Size, srcImage.Frames.Select(im => im.Data.Select(px => (byte)remap[px]).ToArray())); + } } static int ColorDistance(uint a, uint b) diff --git a/OpenRA.Mods.Common/UtilityCommands/TransposeShpCommand.cs b/OpenRA.Mods.Common/UtilityCommands/TransposeShpCommand.cs deleted file mode 100644 index 0f5f5be222..0000000000 --- a/OpenRA.Mods.Common/UtilityCommands/TransposeShpCommand.cs +++ /dev/null @@ -1,48 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation. For more information, - * see COPYING. - */ -#endregion - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using OpenRA.FileFormats; - -namespace OpenRA.Mods.Common.UtilityCommands -{ - class TransposeShpCommand : IUtilityCommand - { - public string Name { get { return "--transpose"; } } - - [Desc("SRCSHP DESTSHP START N M [START N M ...]", - "Transpose the N*M block of frames starting at START.")] - public void Run(ModData modData, string[] args) - { - var srcImage = ShpReader.Load(args[1]); - - var srcFrames = srcImage.Frames; - var destFrames = srcImage.Frames.ToArray(); - - for (var z = 3; z < args.Length - 2; z += 3) - { - var start = Exts.ParseIntegerInvariant(args[z]); - var m = Exts.ParseIntegerInvariant(args[z + 1]); - var n = Exts.ParseIntegerInvariant(args[z + 2]); - - for (var i = 0; i < m; i++) - for (var j = 0; j < n; j++) - destFrames[start + i * n + j] = srcFrames[start + j * m + i]; - } - - using (var destStream = File.Create(args[2])) - ShpReader.Write(destStream, srcImage.Size, destFrames.Select(f => f.Data)); - } - } -} diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj index e547daf283..9dc1baf04c 100644 --- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj +++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj @@ -84,6 +84,7 @@ + diff --git a/OpenRA.Game/FileFormats/R8Reader.cs b/OpenRA.Mods.D2k/R8Loader.cs similarity index 64% rename from OpenRA.Game/FileFormats/R8Reader.cs rename to OpenRA.Mods.D2k/R8Loader.cs index 0db3ff57ff..89ac184489 100644 --- a/OpenRA.Game/FileFormats/R8Reader.cs +++ b/OpenRA.Mods.D2k/R8Loader.cs @@ -1,30 +1,33 @@ #region Copyright & License Information /* * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made + * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, * see COPYING. */ #endregion +using System; using System.Collections.Generic; using System.Drawing; using System.IO; +using System.Linq; using OpenRA.Graphics; -namespace OpenRA.FileFormats +namespace OpenRA.Mods.D2k.SpriteLoaders { - public class R8Reader : ISpriteSource + public class R8Loader : ISpriteLoader { - class R8Image : ISpriteFrame + class R8Frame : ISpriteFrame { public Size Size { get; private set; } public Size FrameSize { get; private set; } public float2 Offset { get; private set; } public byte[] Data { get; set; } + public bool DisableExportPadding { get { return true; } } - public R8Image(Stream s) + public R8Frame(Stream s) { // Scan forward until we find some data var type = s.ReadUInt8(); @@ -61,20 +64,41 @@ namespace OpenRA.FileFormats } } - public IReadOnlyList Frames { get; private set; } - public bool CacheWhenLoadingTileset { get { return true; } } - - public readonly int ImageCount; - public R8Reader(Stream stream) + bool IsR8(Stream s) { - var frames = new List(); - while (stream.Position < stream.Length) + var start = s.Position; + + // First byte is nonzero + if (s.ReadUInt8() == 0) { - frames.Add(new R8Image(stream)); - ImageCount++; + s.Position = start; + return false; } - Frames = frames.ToArray().AsReadOnly(); + // Check the format of the first frame + s.Position = start + 25; + var d = s.ReadUInt8(); + + s.Position = start; + return d == 8; + } + + public bool TryParseSprite(Stream s, out ISpriteFrame[] frames) + { + if (!IsR8(s)) + { + frames = null; + return false; + } + + var start = s.Position; + var tmp = new List(); + while (s.Position < s.Length) + tmp.Add(new R8Frame(s)); + s.Position = start; + + frames = tmp.ToArray(); + return true; } } } diff --git a/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj b/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj index 9650418c9d..4dc1e5f23c 100644 --- a/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj +++ b/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj @@ -56,6 +56,7 @@ +