Merge pull request #6673 from pchote/unhardcode-sprites

Move sprite parsers into mod code
This commit is contained in:
obrakmann
2014-10-07 20:45:50 +02:00
36 changed files with 695 additions and 682 deletions

View File

@@ -166,7 +166,7 @@ mod_ts: $(mod_ts_TARGET)
editor_SRCS := $(shell find OpenRA.Editor/ -iname '*.cs') editor_SRCS := $(shell find OpenRA.Editor/ -iname '*.cs')
editor_TARGET = OpenRA.Editor.exe editor_TARGET = OpenRA.Editor.exe
editor_KIND = winexe 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_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_EXTRA = -resource:OpenRA.Editor.Form1.resources -resource:OpenRA.Editor.MapSelect.resources
editor_FLAGS = -win32icon:OpenRA.Editor/OpenRA.Editor.Icon.ico editor_FLAGS = -win32icon:OpenRA.Editor/OpenRA.Editor.Icon.ico

View File

@@ -160,6 +160,10 @@
<Project>{0DFB103F-2962-400F-8C6D-E2C28CCBA633}</Project> <Project>{0DFB103F-2962-400F-8C6D-E2C28CCBA633}</Project>
<Name>OpenRA.Game</Name> <Name>OpenRA.Game</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\OpenRA.Mods.Common\OpenRA.Mods.Common.csproj">
<Project>{FE6C8CC0-2F07-442A-B29F-17617B3B7FC6}</Project>
<Name>OpenRA.Mods.Common</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="OpenRA.Editor.Icon.ico" /> <Content Include="OpenRA.Editor.Icon.ico" />

View File

@@ -14,13 +14,14 @@ using System.Linq;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.FileSystem; using OpenRA.FileSystem;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Mods.Common.SpriteLoaders;
using OpenRA.Traits; using OpenRA.Traits;
namespace OpenRA.Editor namespace OpenRA.Editor
{ {
static class RenderUtils static class RenderUtils
{ {
static Bitmap RenderShp(ISpriteSource shp, IPalette p) static Bitmap RenderShp(ShpTDSprite shp, IPalette p)
{ {
var frame = shp.Frames.First(); var frame = shp.Frames.First();
@@ -50,14 +51,14 @@ namespace OpenRA.Editor
var image = info.Traits.Get<ILegacyEditorRenderInfo>().EditorImage(info); var image = info.Traits.Get<ILegacyEditorRenderInfo>().EditorImage(info);
using (var s = GlobalFileSystem.OpenWithExts(image, tileset.Extensions)) using (var s = GlobalFileSystem.OpenWithExts(image, tileset.Extensions))
{ {
var shp = new ShpReader(s); var shp = new ShpTDSprite(s);
var bitmap = RenderShp(shp, p); var bitmap = RenderShp(shp, p);
try try
{ {
using (var s2 = GlobalFileSystem.OpenWithExts(image + "2", tileset.Extensions)) using (var s2 = GlobalFileSystem.OpenWithExts(image + "2", tileset.Extensions))
{ {
var shp2 = new ShpReader(s2); var shp2 = new ShpTDSprite(s2);
var roofBitmap = RenderShp(shp2, p); var roofBitmap = RenderShp(shp2, p);
using (var g = System.Drawing.Graphics.FromImage(bitmap)) using (var g = System.Drawing.Graphics.FromImage(bitmap))
@@ -81,7 +82,7 @@ namespace OpenRA.Editor
using (var s = GlobalFileSystem.OpenWithExts(image, exts)) using (var s = GlobalFileSystem.OpenWithExts(image, exts))
{ {
// TODO: Do this properly // TODO: Do this properly
var shp = new ShpReader(s) as ISpriteSource; var shp = new ShpTDSprite(s);
var frame = shp.Frames.Last(); var frame = shp.Frames.Last();
var bitmap = new Bitmap(frame.Size.Width, frame.Size.Height, PixelFormat.Format8bppIndexed); var bitmap = new Bitmap(frame.Size.Width, frame.Size.Height, PixelFormat.Format8bppIndexed);

View File

@@ -271,18 +271,19 @@ namespace OpenRA.Editor
for (var i = 0; i < ChunkSize; i++) for (var i = 0; i < ChunkSize; i++)
for (var j = 0; j < ChunkSize; j++) for (var j = 0; j < ChunkSize; j++)
{ {
var cell = new CPos(u * ChunkSize + i, v * ChunkSize + j); var ui = u * ChunkSize + i;
var tr = Map.MapTiles.Value[cell]; var vj = v * ChunkSize + j;
var tr = Map.MapTiles.Value[ui, vj];
var tile = TileSetRenderer.Data(tr.Type); 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]; var rawImage = tile[index];
for (var x = 0; x < TileSetRenderer.TileSize; x++) for (var x = 0; x < TileSetRenderer.TileSize; x++)
for (var y = 0; y < TileSetRenderer.TileSize; y++) 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(); 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(), var srcdata = resourceImage.LockBits(resourceImage.Bounds(),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

View File

@@ -22,7 +22,7 @@ namespace OpenRA.Editor
{ {
public readonly int TileSize; public readonly int TileSize;
public TileSet TileSet; public TileSet TileSet;
Dictionary<ushort, List<byte[]>> templates; Dictionary<ushort, byte[][]> templates;
// Extract a square tile that the editor can render // Extract a square tile that the editor can render
byte[] ExtractSquareTile(ISpriteFrame frame) byte[] ExtractSquareTile(ISpriteFrame frame)
@@ -44,42 +44,19 @@ namespace OpenRA.Editor
return data; return data;
} }
List<byte[]> LoadTemplate(string filename, string[] exts, Dictionary<string, ISpriteSource> 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<byte[]>();
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) public TileSetRenderer(TileSet tileset, Size tileSize)
{ {
this.TileSet = tileset; this.TileSet = tileset;
this.TileSize = Math.Min(tileSize.Width, tileSize.Height); this.TileSize = Math.Min(tileSize.Width, tileSize.Height);
templates = new Dictionary<ushort, List<byte[]>>(); templates = new Dictionary<ushort, byte[][]>();
var sourceCache = new Dictionary<string, ISpriteSource>(); var spriteLoader = new SpriteLoader(Game.modData.SpriteLoaders, tileset.Extensions, null);
foreach (var t in TileSet.Templates) foreach (var t in tileset.Templates)
templates.Add(t.Key, 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 => ExtractSquareTile(f)).ToArray());
}
} }
public Bitmap RenderTemplate(ushort id, IPalette p) public Bitmap RenderTemplate(ushort id, IPalette p)
@@ -125,7 +102,7 @@ namespace OpenRA.Editor
return bitmap; return bitmap;
} }
public List<byte[]> Data(ushort id) public byte[][] Data(ushort id)
{ {
return templates[id]; return templates[id];
} }

View File

@@ -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<ISpriteFrame> 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);
}
}
}
}

View File

@@ -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<ISpriteFrame> 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);
}
}
}
}

View File

@@ -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<ISpriteFrame> 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<TmpTSTile>();
for (var i = 0; i < offsets.Length; i++)
{
s.Position = offsets[i];
tiles.Add(new TmpTSTile(s, size));
}
Frames = tiles.ToArray().AsReadOnly();
}
}
}

View File

@@ -45,7 +45,7 @@ namespace OpenRA.Graphics
foreach (var p in nodesDict["Palettes"].Nodes) foreach (var p in nodesDict["Palettes"].Nodes)
palette.AddPalette(p.Key, new ImmutablePalette(GlobalFileSystem.Open(p.Value.Value), shadowIndex), false); 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) foreach (var s in nodesDict["Cursors"].Nodes)
LoadSequencesForCursor(spriteLoader, s.Key, s.Value); LoadSequencesForCursor(spriteLoader, s.Key, s.Value);
spriteLoader.SheetBuilder.Current.ReleaseBuffer(); spriteLoader.SheetBuilder.Current.ReleaseBuffer();

View File

@@ -79,7 +79,7 @@ namespace OpenRA.Graphics
{ {
this.modData = modData; 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) public Sequences LoadSequences(Map map)

View File

@@ -8,35 +8,67 @@
*/ */
#endregion #endregion
using System.Drawing;
using System.IO;
using System.Linq; using System.Linq;
using OpenRA.FileSystem; using OpenRA.FileSystem;
using OpenRA.Primitives; using OpenRA.Primitives;
namespace OpenRA.Graphics 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 class SpriteLoader
{ {
public readonly SheetBuilder SheetBuilder; public readonly SheetBuilder SheetBuilder;
readonly ISpriteLoader[] loaders;
readonly Cache<string, Sprite[]> sprites; readonly Cache<string, Sprite[]> sprites;
readonly Cache<string, ISpriteFrame[]> frames;
readonly string[] exts; readonly string[] exts;
public SpriteLoader(string[] exts, SheetBuilder sheetBuilder) public SpriteLoader(ISpriteLoader[] loaders, string[] exts, SheetBuilder sheetBuilder)
{ {
this.loaders = loaders;
SheetBuilder = sheetBuilder; SheetBuilder = sheetBuilder;
// Include extension-less version // Include extension-less version
this.exts = exts.Append("").ToArray(); this.exts = exts.Append("").ToArray();
sprites = new Cache<string, Sprite[]>(CacheSpriteFrames); sprites = new Cache<string, Sprite[]>(CacheSprites);
frames = new Cache<string, ISpriteFrame[]>(CacheFrames);
} }
Sprite[] CacheSpriteFrames(string filename) Sprite[] CacheSprites(string filename)
{ {
using (var stream = GlobalFileSystem.OpenWithExts(filename, exts)) return frames[filename].Select(a => SheetBuilder.Add(a))
return SpriteSource.LoadSpriteSource(stream, filename).Frames
.Select(a => SheetBuilder.Add(a))
.ToArray(); .ToArray();
} }
ISpriteFrame[] CacheFrames(string filename)
{
using (var stream = GlobalFileSystem.OpenWithExts(filename, exts))
{
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 Sprite[] LoadAllSprites(string filename) { return sprites[filename]; }
public ISpriteFrame[] LoadAllFrames(string filename) { return frames[filename]; }
} }
} }

View File

@@ -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<ISpriteFrame> 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");
}
}
}
}

View File

@@ -22,33 +22,6 @@ namespace OpenRA.Graphics
Dictionary<ushort, Sprite[]> templates; Dictionary<ushort, Sprite[]> templates;
Sprite missingTile; Sprite missingTile;
Sprite[] LoadTemplate(string filename, string[] exts, Dictionary<string, ISpriteSource> 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<Sprite>();
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) public Theater(TileSet tileset)
{ {
var allocated = false; var allocated = false;
@@ -61,11 +34,17 @@ namespace OpenRA.Graphics
return new Sheet(new Size(tileset.SheetSize, tileset.SheetSize), true); return new Sheet(new Size(tileset.SheetSize, tileset.SheetSize), true);
}; };
var sourceCache = new Dictionary<string, ISpriteSource>();
templates = new Dictionary<ushort, Sprite[]>();
sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate); sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate);
templates = new Dictionary<ushort, Sprite[]>();
// 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) 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 // 1x1px transparent tile
missingTile = sheetBuilder.Add(new byte[1], new Size(1, 1)); missingTile = sheetBuilder.Add(new byte[1], new Size(1, 1));

View File

@@ -37,6 +37,8 @@ namespace OpenRA
public readonly Size TileSize = new Size(24, 24); public readonly Size TileSize = new Size(24, 24);
public readonly TileShape TileShape = TileShape.Rectangle; 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")] [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 = public readonly WVec[] SubCellOffsets =
{ {
@@ -128,6 +130,9 @@ namespace OpenRA
compat.Add(c.Trim()); compat.Add(c.Trim());
MapCompatibility = compat.ToArray(); MapCompatibility = compat.ToArray();
if (yaml.ContainsKey("SpriteFormats"))
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", yaml["SpriteFormats"].Value);
} }
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key) static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key)

View File

@@ -457,7 +457,7 @@ namespace OpenRA
for (var i = 0; i < MapSize.X; i++) for (var i = 0; i < MapSize.X; i++)
for (var j = 0; j < MapSize.Y; j++) 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.Type);
writer.Write(tile.Index); writer.Write(tile.Index);
} }
@@ -467,7 +467,7 @@ namespace OpenRA
{ {
for (var j = 0; j < MapSize.Y; j++) 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.Type);
writer.Write(tile.Index); writer.Write(tile.Index);
} }
@@ -654,9 +654,8 @@ namespace OpenRA
{ {
for (var i = Bounds.Left; i < Bounds.Right; i++) for (var i = Bounds.Left; i < Bounds.Right; i++)
{ {
var cell = new CPos(i, j); var type = MapTiles.Value[i, j].Type;
var type = MapTiles.Value[cell].Type; var index = MapTiles.Value[i, j].Index;
var index = MapTiles.Value[cell].Index;
if (!tileset.Templates.ContainsKey(type)) if (!tileset.Templates.ContainsKey(type))
{ {
Console.WriteLine("Unknown Tile ID {0}".F(type)); Console.WriteLine("Unknown Tile ID {0}".F(type));
@@ -668,7 +667,7 @@ namespace OpenRA
continue; continue;
index = (byte)r.Next(0, template.TilesCount); 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 j = -max; j <= max; j++)
for (var i = -max; i <= max; i++) for (var i = -max; i <= max; i++)
if (max * max >= i * i + j * j) 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 // Sort each integer-distance group by the actual distance
foreach (var list in ts) foreach (var list in ts)

View File

@@ -24,6 +24,7 @@ namespace OpenRA
public readonly ObjectCreator ObjectCreator; public readonly ObjectCreator ObjectCreator;
public readonly WidgetLoader WidgetLoader; public readonly WidgetLoader WidgetLoader;
public readonly MapCache MapCache; public readonly MapCache MapCache;
public readonly ISpriteLoader[] SpriteLoaders;
public ILoadScreen LoadScreen = null; public ILoadScreen LoadScreen = null;
public VoxelLoader VoxelLoader; public VoxelLoader VoxelLoader;
public readonly RulesetCache RulesetCache; public readonly RulesetCache RulesetCache;
@@ -45,6 +46,18 @@ namespace OpenRA
RulesetCache.LoadingProgress += HandleLoadingProgress; RulesetCache.LoadingProgress += HandleLoadingProgress;
MapCache = new MapCache(this); MapCache = new MapCache(this);
var loaders = new List<ISpriteLoader>();
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 // HACK: Mount only local folders so we have a half-working environment for the asset installer
GlobalFileSystem.UnmountAll(); GlobalFileSystem.UnmountAll();
foreach (var dir in Manifest.Folders) foreach (var dir in Manifest.Folders)

View File

@@ -277,13 +277,6 @@
<Compile Include="FileFormats\XccLocalDatabase.cs" /> <Compile Include="FileFormats\XccLocalDatabase.cs" />
<Compile Include="FileFormats\HvaReader.cs" /> <Compile Include="FileFormats\HvaReader.cs" />
<Compile Include="FileFormats\PngLoader.cs" /> <Compile Include="FileFormats\PngLoader.cs" />
<Compile Include="FileFormats\R8Reader.cs" />
<Compile Include="FileFormats\ShpD2Reader.cs" />
<Compile Include="FileFormats\ShpReader.cs" />
<Compile Include="FileFormats\ShpTSReader.cs" />
<Compile Include="FileFormats\TmpRAReader.cs" />
<Compile Include="FileFormats\TmpTDReader.cs" />
<Compile Include="FileFormats\TmpTSReader.cs" />
<Compile Include="FileFormats\VqaReader.cs" /> <Compile Include="FileFormats\VqaReader.cs" />
<Compile Include="FileFormats\VxlReader.cs" /> <Compile Include="FileFormats\VxlReader.cs" />
<Compile Include="Primitives\ActionQueue.cs" /> <Compile Include="Primitives\ActionQueue.cs" />
@@ -320,7 +313,6 @@
<Compile Include="Map\ActorReference.cs" /> <Compile Include="Map\ActorReference.cs" />
<Compile Include="Support\Evaluator.cs" /> <Compile Include="Support\Evaluator.cs" />
<Compile Include="Settings.cs" /> <Compile Include="Settings.cs" />
<Compile Include="Graphics\SpriteSource.cs" />
<Compile Include="Graphics\PlayerColorRemap.cs" /> <Compile Include="Graphics\PlayerColorRemap.cs" />
<Compile Include="Graphics\Palette.cs" /> <Compile Include="Graphics\Palette.cs" />
<Compile Include="FileSystem\GlobalFileSystem.cs" /> <Compile Include="FileSystem\GlobalFileSystem.cs" />

View File

@@ -87,7 +87,6 @@
<Compile Include="UtilityCommands\ImportLegacyMapCommand.cs" /> <Compile Include="UtilityCommands\ImportLegacyMapCommand.cs" />
<Compile Include="UtilityCommands\LegacyMapImporter.cs" /> <Compile Include="UtilityCommands\LegacyMapImporter.cs" />
<Compile Include="UtilityCommands\RemapShpCommand.cs" /> <Compile Include="UtilityCommands\RemapShpCommand.cs" />
<Compile Include="UtilityCommands\TransposeShpCommand.cs" />
<Compile Include="UtilityCommands\UpgradeMapCommand.cs" /> <Compile Include="UtilityCommands\UpgradeMapCommand.cs" />
<Compile Include="UtilityCommands\UpgradeModCommand.cs" /> <Compile Include="UtilityCommands\UpgradeModCommand.cs" />
<Compile Include="UtilityCommands\UpgradeRules.cs" /> <Compile Include="UtilityCommands\UpgradeRules.cs" />
@@ -114,6 +113,11 @@
<Compile Include="World\ResourceClaim.cs" /> <Compile Include="World\ResourceClaim.cs" />
<Compile Include="World\ResourceClaimLayer.cs" /> <Compile Include="World\ResourceClaimLayer.cs" />
<Compile Include="World\SmudgeLayer.cs" /> <Compile Include="World\SmudgeLayer.cs" />
<Compile Include="SpriteLoaders\ShpTDLoader.cs" />
<Compile Include="SpriteLoaders\ShpTSLoader.cs" />
<Compile Include="SpriteLoaders\TmpRALoader.cs" />
<Compile Include="SpriteLoaders\TmpTDLoader.cs" />
<Compile Include="SpriteLoaders\ShpD2Loader.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>

View File

@@ -9,13 +9,16 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Graphics; using OpenRA.Graphics;
namespace OpenRA.FileFormats namespace OpenRA.Mods.Common.SpriteLoaders
{ {
public class ShpD2Reader : ISpriteSource public class ShpD2Loader : ISpriteLoader
{ {
[Flags] enum FormatFlags : int [Flags] enum FormatFlags : int
{ {
@@ -24,14 +27,15 @@ namespace OpenRA.FileFormats
VariableLengthTable = 4 VariableLengthTable = 4
} }
class Frame : ISpriteFrame class ShpD2Frame : ISpriteFrame
{ {
public Size Size { get; private set; } public Size Size { get; private set; }
public Size FrameSize { get { return Size; } } public Size FrameSize { get { return Size; } }
public float2 Offset { get { return float2.Zero; } } public float2 Offset { get { return float2.Zero; } }
public byte[] Data { get; set; } public byte[] Data { get; set; }
public bool DisableExportPadding { get { return false; } }
public Frame(Stream s) public ShpD2Frame(Stream s)
{ {
var flags = (FormatFlags)s.ReadUInt16(); var flags = (FormatFlags)s.ReadUInt16();
s.Position += 1; s.Position += 1;
@@ -83,11 +87,48 @@ namespace OpenRA.FileFormats
} }
} }
public IReadOnlyList<ISpriteFrame> Frames { get; private set; } bool IsShpD2(Stream s)
public bool CacheWhenLoadingTileset { get { return false; } }
public ShpD2Reader(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(); var imageCount = s.ReadUInt16();
// Last offset is pointer to end of file. // Last offset is pointer to end of file.
@@ -101,13 +142,27 @@ namespace OpenRA.FileFormats
for (var i = 0; i < imageCount + 1; i++) for (var i = 0; i < imageCount + 1; i++)
offsets[i] = (twoByteOffset ? s.ReadUInt16() : s.ReadUInt32()) + 2; offsets[i] = (twoByteOffset ? s.ReadUInt16() : s.ReadUInt32()) + 2;
var frames = new Frame[imageCount]; var frames = new ShpD2Frame[imageCount];
Frames = frames.AsReadOnly();
for (var i = 0; i < frames.Length; i++) for (var i = 0; i < frames.Length; i++)
{ {
s.Position = offsets[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;
} }
} }
} }

View File

@@ -13,11 +13,63 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Graphics; 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 } enum Format { Format20 = 0x20, Format40 = 0x40, Format80 = 0x80 }
@@ -27,6 +79,7 @@ namespace OpenRA.FileFormats
public Size FrameSize { get { return reader.Size; } } public Size FrameSize { get { return reader.Size; } }
public float2 Offset { get { return float2.Zero; } } public float2 Offset { get { return float2.Zero; } }
public byte[] Data { get; set; } public byte[] Data { get; set; }
public bool DisableExportPadding { get { return false; } }
public uint FileOffset; public uint FileOffset;
public Format Format; public Format Format;
@@ -35,12 +88,12 @@ namespace OpenRA.FileFormats
public Format RefFormat; public Format RefFormat;
public ImageHeader RefImage; public ImageHeader RefImage;
ShpReader reader; ShpTDSprite reader;
// Used by ShpWriter // Used by ShpWriter
public ImageHeader() { } public ImageHeader() { }
public ImageHeader(Stream stream, ShpReader reader) public ImageHeader(Stream stream, ShpTDSprite reader)
{ {
this.reader = reader; this.reader = reader;
var data = stream.ReadUInt32(); var data = stream.ReadUInt32();
@@ -60,7 +113,6 @@ namespace OpenRA.FileFormats
} }
public IReadOnlyList<ISpriteFrame> Frames { get; private set; } public IReadOnlyList<ISpriteFrame> Frames { get; private set; }
public bool CacheWhenLoadingTileset { get { return false; } }
public readonly Size Size; public readonly Size Size;
int recurseDepth = 0; int recurseDepth = 0;
@@ -69,7 +121,7 @@ namespace OpenRA.FileFormats
readonly long shpBytesFileOffset; readonly long shpBytesFileOffset;
readonly byte[] shpBytes; readonly byte[] shpBytes;
public ShpReader(Stream stream) public ShpTDSprite(Stream stream)
{ {
imageCount = stream.ReadUInt16(); imageCount = stream.ReadUInt16();
stream.Position += 4; stream.Position += 4;
@@ -149,12 +201,6 @@ namespace OpenRA.FileFormats
return imageData; 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<byte[]> frames) public static void Write(Stream s, Size size, IEnumerable<byte[]> frames)
{ {
var compressedFrames = frames.Select(f => Format80.Encode(f)).ToArray(); var compressedFrames = frames.Select(f => Format80.Encode(f)).ToArray();

View File

@@ -8,25 +8,30 @@
*/ */
#endregion #endregion
using System;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Graphics; 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 Size { get; private set; }
public Size FrameSize { get; private set; } public Size FrameSize { get; private set; }
public float2 Offset { get; private set; } public float2 Offset { get; private set; }
public byte[] Data { get; set; } public byte[] Data { get; set; }
public bool DisableExportPadding { get { return false; } }
public readonly uint FileOffset; public readonly uint FileOffset;
public readonly byte Format; public readonly byte Format;
public FrameHeader(Stream stream, Size frameSize) public ShpTSFrame(Stream stream, Size frameSize)
{ {
var x = stream.ReadUInt16(); var x = stream.ReadUInt16();
var y = stream.ReadUInt16(); var y = stream.ReadUInt16();
@@ -44,21 +49,56 @@ namespace OpenRA.FileFormats
} }
} }
public IReadOnlyList<ISpriteFrame> Frames { get; private set; } bool IsShpTS(Stream s)
public bool CacheWhenLoadingTileset { get { return false; } }
public ShpTSReader(Stream stream)
{ {
stream.ReadUInt16(); var start = s.Position;
var width = stream.ReadUInt16();
var height = stream.ReadUInt16();
var size = new Size(width, height);
var frameCount = stream.ReadUInt16();
var frames = new FrameHeader[frameCount]; // First word is zero
Frames = frames.AsReadOnly(); 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++) 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++) for (var i = 0; i < frameCount; i++)
{ {
@@ -66,13 +106,13 @@ namespace OpenRA.FileFormats
if (f.FileOffset == 0) if (f.FileOffset == 0)
continue; continue;
stream.Position = f.FileOffset; s.Position = f.FileOffset;
var frameSize = f.Size.Width * f.Size.Height; var frameSize = f.Size.Width * f.Size.Height;
// Uncompressed // Uncompressed
if (f.Format == 1 || f.Format == 0) if (f.Format == 1 || f.Format == 0)
f.Data = stream.ReadBytes(frameSize); f.Data = s.ReadBytes(frameSize);
// Uncompressed scanlines // Uncompressed scanlines
else if (f.Format == 2) else if (f.Format == 2)
@@ -80,9 +120,9 @@ namespace OpenRA.FileFormats
f.Data = new byte[frameSize]; f.Data = new byte[frameSize];
for (var j = 0; j < f.Size.Height; j++) 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; 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]; f.Data = new byte[frameSize];
for (var j = 0; j < f.Size.Height; j++) 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; 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;
} }
} }
} }

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -17,6 +17,7 @@ using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.Mods.Common.SpriteLoaders;
namespace OpenRA.Mods.Common.UtilityCommands namespace OpenRA.Mods.Common.UtilityCommands
{ {
@@ -36,7 +37,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
throw new InvalidOperationException("All frames must be the same size"); throw new InvalidOperationException("All frames must be the same size");
using (var destStream = File.Create(dest)) 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."); Console.WriteLine(dest + " saved.");
} }

View File

@@ -41,19 +41,17 @@ namespace OpenRA.Mods.Common.UtilityCommands
var palette = new ImmutablePalette(args[2], shadowIndex); var palette = new ImmutablePalette(args[2], shadowIndex);
ISpriteSource source; var frames = new SpriteLoader(modData.SpriteLoaders, new string[0], null)
using (var stream = File.OpenRead(src)) .LoadAllFrames(src);
source = SpriteSource.LoadSpriteSource(stream, src);
// The r8 padding requires external information that we can't access here. var usePadding = !args.Contains("--nopadding");
var usePadding = !(args.Contains("--nopadding") || source is R8Reader);
var count = 0; var count = 0;
var prefix = Path.GetFileNameWithoutExtension(src); var prefix = Path.GetFileNameWithoutExtension(src);
foreach (var frame in source.Frames) foreach (var frame in frames)
{ {
var frameSize = usePadding ? frame.FrameSize : frame.Size; var frameSize = usePadding && !frame.DisableExportPadding ? frame.FrameSize : frame.Size;
var offset = usePadding ? (frame.Offset - 0.5f * new float2(frame.Size - frame.FrameSize)).ToInt2() : int2.Zero; var offset = usePadding && !frame.DisableExportPadding ? (frame.Offset - 0.5f * new float2(frame.Size - frame.FrameSize)).ToInt2() : int2.Zero;
// shp(ts) may define empty frames // shp(ts) may define empty frames
if (frameSize.Width == 0 && frameSize.Height == 0) if (frameSize.Width == 0 && frameSize.Height == 0)
@@ -69,7 +67,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
// Clear the frame // Clear the frame
if (usePadding) if (usePadding && !frame.DisableExportPadding)
{ {
var clearRow = new byte[data.Stride]; var clearRow = new byte[data.Stride];
for (var i = 0; i < frameSize.Height; i++) for (var i = 0; i < frameSize.Height; i++)

View File

@@ -18,6 +18,7 @@ using OpenRA.Traits;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.FileSystem; using OpenRA.FileSystem;
using OpenRA.Mods.Common.SpriteLoaders;
namespace OpenRA.Mods.Common.UtilityCommands namespace OpenRA.Mods.Common.UtilityCommands
{ {
@@ -65,12 +66,14 @@ namespace OpenRA.Mods.Common.UtilityCommands
.Where(a => !remap.ContainsValue(a)) .Where(a => !remap.ContainsValue(a))
.MinBy(a => ColorDistance(destPalette[a], srcPalette[i])); .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])) 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())); srcImage.Frames.Select(im => im.Data.Select(px => (byte)remap[px]).ToArray()));
} }
}
static int ColorDistance(uint a, uint b) static int ColorDistance(uint a, uint b)
{ {

View File

@@ -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));
}
}
}

View File

@@ -84,6 +84,7 @@
<Compile Include="Widgets\SlidingContainerWidget.cs" /> <Compile Include="Widgets\SlidingContainerWidget.cs" />
<Compile Include="Widgets\Logic\IngameChromeLogic.cs" /> <Compile Include="Widgets\Logic\IngameChromeLogic.cs" />
<Compile Include="ChooseBuildTabOnSelect.cs" /> <Compile Include="ChooseBuildTabOnSelect.cs" />
<Compile Include="R8Loader.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>

View File

@@ -8,23 +8,26 @@
*/ */
#endregion #endregion
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq;
using OpenRA.Graphics; 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 Size { get; private set; }
public Size FrameSize { get; private set; } public Size FrameSize { get; private set; }
public float2 Offset { get; private set; } public float2 Offset { get; private set; }
public byte[] Data { get; 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 // Scan forward until we find some data
var type = s.ReadUInt8(); var type = s.ReadUInt8();
@@ -61,20 +64,41 @@ namespace OpenRA.FileFormats
} }
} }
public IReadOnlyList<ISpriteFrame> Frames { get; private set; } bool IsR8(Stream s)
public bool CacheWhenLoadingTileset { get { return true; } } {
var start = s.Position;
public readonly int ImageCount; // First byte is nonzero
public R8Reader(Stream stream) if (s.ReadUInt8() == 0)
{ {
var frames = new List<R8Image>(); s.Position = start;
while (stream.Position < stream.Length) return false;
{
frames.Add(new R8Image(stream));
ImageCount++;
} }
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<R8Frame>();
while (s.Position < s.Length)
tmp.Add(new R8Frame(s));
s.Position = start;
frames = tmp.ToArray();
return true;
} }
} }
} }

View File

@@ -56,6 +56,7 @@
<Compile Include="TiberianSunRefinery.cs" /> <Compile Include="TiberianSunRefinery.cs" />
<Compile Include="Render\WithVoxelWalkerBody.cs" /> <Compile Include="Render\WithVoxelWalkerBody.cs" />
<Compile Include="Render\WithVoxelUnloadBody.cs" /> <Compile Include="Render\WithVoxelUnloadBody.cs" />
<Compile Include="SpriteLoaders\TmpTSLoader.cs" />
</ItemGroup> </ItemGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -0,0 +1,121 @@
#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.TS.SpriteLoaders
{
public class TmpTSLoader : ISpriteLoader
{
class TmpTSFrame : 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 TmpTSFrame(Stream s, Size size)
{
if (s.Position != 0)
{
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
}
else
Data = new byte[0];
}
}
bool IsTmpTS(Stream s)
{
var start = s.Position;
s.Position += 8;
var sx = s.ReadUInt32();
var sy = s.ReadUInt32();
// Find the first non-empty frame
var offset = s.ReadUInt32();
while (offset == 0)
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;
}
TmpTSFrame[] ParseFrames(Stream s)
{
var start = s.Position;
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 TmpTSFrame[offsets.Length];
for (var i = 0; i < offsets.Length; i++)
{
s.Position = offsets[i];
tiles[i] = new TmpTSFrame(s, size);
}
s.Position = start;
return tiles;
}
public bool TryParseSprite(Stream s, out ISpriteFrame[] frames)
{
if (!IsTmpTS(s))
{
frames = null;
return false;
}
frames = ParseFrames(s);
return true;
}
}
}

View File

@@ -210,3 +210,5 @@ Missions:
mods/cnc/missions.yaml mods/cnc/missions.yaml
SupportsMapsFrom: cnc SupportsMapsFrom: cnc
SpriteFormats: ShpTD, TmpTD, ShpTS, TmpRA

View File

@@ -188,3 +188,5 @@ LuaScripts:
mods/common/lua/facing.lua mods/common/lua/facing.lua
SupportsMapsFrom: d2k SupportsMapsFrom: d2k
SpriteFormats: R8, ShpTD, TmpRA

View File

@@ -50,3 +50,5 @@ Fonts:
Size:10 Size:10
LobbyDefaults: LobbyDefaults:
SpriteFormats: ShpTD

View File

@@ -207,3 +207,5 @@ Missions:
mods/ra/missions.yaml mods/ra/missions.yaml
SupportsMapsFrom: ra SupportsMapsFrom: ra
SpriteFormats: ShpTD, TmpRA, TmpTD, ShpTS

View File

@@ -229,3 +229,5 @@ LuaScripts:
mods/common/lua/facing.lua mods/common/lua/facing.lua
SupportsMapsFrom: ts SupportsMapsFrom: ts
SpriteFormats: ShpTS, TmpTS, ShpTD