Merge pull request #4185 from pchote/frame-rework

Rewrite sprite handling.
This commit is contained in:
Matthias Mailänder
2013-11-30 14:46:54 -08:00
55 changed files with 1623 additions and 2439 deletions

View File

@@ -16,8 +16,9 @@ NEW:
Added a setting for team health bar colors.
Asset Browser:
Fixed crashes when trying to load invalid filenames or sprites with just 1 frame.
Added support for browsing the folders for R8 files.
Added support for all sprite types.
Added palette chooser and colorpicker dropdown boxes.
Overhauled layout.
Red Alert:
Added MAD Tank.
Fixed a crash in Monster Tank Madness.
@@ -86,6 +87,10 @@ NEW:
Fixed corrupted replays (which would immediately desync).
Removed runtime mod merging.
Added support for map scripting with Lua.
Overhauled sprite loading code.
Improved error message when loading corrupted sprites.
Rewritten shp(ts) parser makes more efficient use of texture space.
Added support for the dune 2 shp format.
Build system and packages:
Added GeoIP to Makefile so it is installed properly.
Added desktop shortcut creation support to the Makefile and Windows installer.
@@ -113,7 +118,13 @@ NEW:
Added InvalidTargets property to weapons.
Added modifier support for build palette hotkeys.
The Requires: option for inheriting from a parent mod has been removed. Mods can directly reference the parent mod files instead.
Icons are now defined in Sequences not Rules.
Icons definitions have moved from the unit's rules to its sequence.
Mouse cursors (cursors.yaml) must now specify their file extension.
OpenRA.Utility --png will now generate a set of frames for any sprite type [shp(td)/shp(ts)/shp(d2)/r8/tmp(td)/tmp(ra)].
OpenRA.Utility --shp now requires a list of frames to be combined into a shp.
Removed Utility --tmp-png, --r8, --fromd2 commands (use --png instead).
Removed Asset Browser file extraction / conversion (use the Utility instead).
Unified sprite loading allows any sprite type to be used anywhere: shp can now be used for terrain, and tmp for units.
20130915:
All mods:

View File

@@ -159,8 +159,8 @@ STD_MOD_DEPS = $(STD_MOD_LIBS) $(ralint_TARGET)
mod_ra_SRCS := $(shell find OpenRA.Mods.RA/ -iname '*.cs')
mod_ra_TARGET = mods/ra/OpenRA.Mods.RA.dll
mod_ra_KIND = library
mod_ra_DEPS = $(STD_MOD_DEPS) $(utility_TARGET) $(geoip_TARGET) $(irc_TARGET) $(lua_TARGET)
mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(utility_TARGET) $(geoip_TARGET) $(irc_TARGET) $(lua_TARGET)
mod_ra_DEPS = $(STD_MOD_DEPS) $(geoip_TARGET) $(irc_TARGET) $(lua_TARGET)
mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(geoip_TARGET) $(irc_TARGET) $(lua_TARGET)
PROGRAMS += mod_ra
mod_ra: $(mod_ra_TARGET)

View File

@@ -165,6 +165,7 @@
<Compile Include="Surface.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="TileSetRenderer.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -8,8 +8,10 @@
*/
#endregion
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Traits;
@@ -17,11 +19,11 @@ namespace OpenRA.Editor
{
static class RenderUtils
{
static Bitmap RenderShp(ShpReader shp, Palette p)
static Bitmap RenderShp(ISpriteSource shp, Palette p)
{
var frame = shp[0];
var frame = shp.Frames.First();
var bitmap = new Bitmap(shp.Width, shp.Height, PixelFormat.Format8bppIndexed);
var bitmap = new Bitmap(frame.Size.Width, frame.Size.Height, PixelFormat.Format8bppIndexed);
bitmap.Palette = p.AsSystemPalette();
@@ -33,9 +35,9 @@ namespace OpenRA.Editor
byte* q = (byte*)data.Scan0.ToPointer();
var stride2 = data.Stride;
for (var i = 0; i < shp.Width; i++)
for (var j = 0; j < shp.Height; j++)
q[j * stride2 + i] = frame.Image[i + shp.Width * j];
for (var i = 0; i < frame.Size.Width; i++)
for (var j = 0; j < frame.Size.Height; j++)
q[j * stride2 + i] = frame.Data[i + frame.Size.Width * j];
}
bitmap.UnlockBits(data);
@@ -78,10 +80,11 @@ namespace OpenRA.Editor
var image = info.SpriteNames[0];
using (var s = FileSystem.OpenWithExts(image, exts))
{
var shp = new ShpReader(s);
var frame = shp[shp.ImageCount - 1];
// TODO: Do this properly
var shp = new ShpReader(s) as ISpriteSource;
var frame = shp.Frames.Last();
var bitmap = new Bitmap(shp.Width, shp.Height, PixelFormat.Format8bppIndexed);
var bitmap = new Bitmap(frame.Size.Width, frame.Size.Height, PixelFormat.Format8bppIndexed);
bitmap.Palette = p.AsSystemPalette();
var data = bitmap.LockBits(bitmap.Bounds(),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
@@ -91,13 +94,13 @@ namespace OpenRA.Editor
byte* q = (byte*)data.Scan0.ToPointer();
var stride = data.Stride;
for (var i = 0; i < shp.Width; i++)
for (var j = 0; j < shp.Height; j++)
q[j * stride + i] = frame.Image[i + shp.Width * j];
for (var i = 0; i < frame.Size.Width; i++)
for (var j = 0; j < frame.Size.Height; j++)
q[j * stride + i] = frame.Data[i + frame.Size.Width * j];
}
bitmap.UnlockBits(data);
return new ResourceTemplate { Bitmap = bitmap, Info = info, Value = shp.ImageCount - 1 };
return new ResourceTemplate { Bitmap = bitmap, Info = info, Value = shp.Frames.Count() - 1 };
}
}
}

View File

@@ -11,9 +11,11 @@
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
namespace OpenRA.FileFormats
namespace OpenRA.Editor
{
public class TileSetRenderer
{
@@ -21,20 +23,31 @@ namespace OpenRA.FileFormats
Dictionary<ushort, List<byte[]>> templates;
public Size TileSize;
List<byte[]> LoadTemplate(string filename, string[] exts, Cache<string, R8Reader> r8cache, int[] frames)
List<byte[]> LoadTemplate(string filename, string[] exts, Dictionary<string, ISpriteSource> sourceCache, int[] frames)
{
if (exts.Contains(".R8") && FileSystem.Exists(filename + ".R8"))
ISpriteSource source;
if (!sourceCache.ContainsKey(filename))
{
var data = new List<byte[]>();
using (var s = FileSystem.OpenWithExts(filename, exts))
source = SpriteSource.LoadSpriteSource(s, filename);
foreach (var f in frames)
data.Add(f >= 0 ? r8cache[filename][f].Image : null);
if (source.CacheWhenLoadingTileset)
sourceCache.Add(filename, source);
}
else
source = sourceCache[filename];
return data;
if (frames != null)
{
var ret = new List<byte[]>();
var srcFrames = source.Frames.ToArray();
foreach (var i in frames)
ret.Add(srcFrames[i].Data);
return ret;
}
using (var s = FileSystem.OpenWithExts(filename, exts))
return new Terrain(s).TileBitmapBytes;
return source.Frames.Select(f => f.Data).ToList();
}
public TileSetRenderer(TileSet tileset, Size tileSize)
@@ -43,9 +56,9 @@ namespace OpenRA.FileFormats
this.TileSize = tileSize;
templates = new Dictionary<ushort, List<byte[]>>();
var r8cache = new Cache<string, R8Reader>(s => new R8Reader(FileSystem.OpenWithExts(s, ".R8")));
var sourceCache = new Dictionary<string, ISpriteSource>();
foreach (var t in TileSet.Templates)
templates.Add(t.Key, LoadTemplate(t.Value.Image, tileset.Extensions, r8cache, t.Value.Frames));
templates.Add(t.Key, LoadTemplate(t.Value.Image, tileset.Extensions, sourceCache, t.Value.Frames));
}
public Bitmap RenderTemplate(ushort id, Palette p)

View File

@@ -1,144 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
namespace OpenRA.FileFormats
{
public enum Dune2ImageFlags : int
{
F80_F2 = 0,
F2 = 2,
L16_F80_F2_1 = 1,
L16_F80_F2_2 = 3,
Ln_F80_F2 = 5
}
public class Dune2ImageHeader
{
public readonly Dune2ImageFlags Flags;
public readonly int Width;
public readonly int Height;
public readonly int Slices;
public readonly int FileSize;
public readonly int DataSize;
public readonly byte[] LookupTable;
public byte[] Image;
public Dune2ImageHeader(Stream s)
{
Flags = (Dune2ImageFlags)s.ReadUInt16();
Slices = s.ReadUInt8();
Width = s.ReadUInt16();
Height = s.ReadUInt8();
FileSize = s.ReadUInt16();
DataSize = s.ReadUInt16();
if (Flags == Dune2ImageFlags.L16_F80_F2_1 ||
Flags == Dune2ImageFlags.L16_F80_F2_2 ||
Flags == Dune2ImageFlags.Ln_F80_F2)
{
int n = Flags == Dune2ImageFlags.Ln_F80_F2 ? s.ReadUInt8() : (byte)16;
LookupTable = new byte[n];
for (int i = 0; i < n; i++)
LookupTable[i] = s.ReadUInt8();
}
else
{
LookupTable = new byte[256];
for (int i = 0; i < 256; i++)
LookupTable[i] = (byte)i;
LookupTable[1] = 0x7f;
LookupTable[2] = 0x7e;
LookupTable[3] = 0x7d;
LookupTable[4] = 0x7c;
}
}
public Size Size
{
get { return new Size(Width, Height); }
}
}
public class Dune2ShpReader : IEnumerable<Dune2ImageHeader>
{
public readonly int ImageCount;
List<Dune2ImageHeader> headers = new List<Dune2ImageHeader>();
public Dune2ShpReader(Stream s)
{
ImageCount = s.ReadUInt16();
//Last offset is pointer to end of file.
uint[] offsets = new uint[ImageCount + 1];
uint temp = s.ReadUInt32();
//If fourth byte in file is non-zero, the offsets are two bytes each.
bool twoByteOffsets = (temp & 0xFF0000) > 0;
if (twoByteOffsets)
{
offsets[0] = ((temp & 0xFFFF0000) >> 16) + 2; //Offset does not account for image count bytes
offsets[1] = (temp & 0xFFFF) + 2;
}
else
offsets[0] = temp + 2;
for (int i = twoByteOffsets ? 2 : 1; i < ImageCount + 1; i++)
offsets[i] = (twoByteOffsets ? s.ReadUInt16() : s.ReadUInt32()) + 2;
for (int i = 0; i < ImageCount; i++)
{
s.Seek(offsets[i], SeekOrigin.Begin);
Dune2ImageHeader header = new Dune2ImageHeader(s);
byte[] imgData = s.ReadBytes(header.FileSize);
header.Image = new byte[header.Height * header.Width];
//Decode image data
if (header.Flags != Dune2ImageFlags.F2)
{
byte[] tempData = new byte[header.DataSize];
Format80.DecodeInto(imgData, tempData);
Format2.DecodeInto(tempData, header.Image);
}
else
Format2.DecodeInto(imgData, header.Image);
//Lookup values in lookup table
if (header.LookupTable != null)
for (int j = 0; j < header.Image.Length; j++)
header.Image[j] = header.LookupTable[header.Image[j]];
headers.Add(header);
}
}
public Dune2ImageHeader this[int index]
{
get { return headers[index]; }
}
public IEnumerator<Dune2ImageHeader> GetEnumerator()
{
return headers.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -12,17 +12,16 @@ using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
namespace OpenRA.FileFormats
{
public class R8Image
class R8Image : ISpriteFrame
{
public readonly Size Size;
public readonly int2 Offset;
public readonly byte[] Image;
// Legacy variable. Can be removed when the utility command is made sensible.
public readonly Size FrameSize;
public Size Size { get; private set; }
public Size FrameSize { get; private set; }
public float2 Offset { get; private set; }
public byte[] Data { get; set; }
public R8Image(Stream s)
{
@@ -52,7 +51,7 @@ namespace OpenRA.FileFormats
// Skip alignment byte
s.ReadUInt8();
Image = s.ReadBytes(width*height);
Data = s.ReadBytes(width*height);
// Ignore palette
if (type == 1 && paletteOffset != 0)
@@ -60,33 +59,20 @@ namespace OpenRA.FileFormats
}
}
public class R8Reader : IEnumerable<R8Image>
public class R8Reader : ISpriteSource
{
readonly List<R8Image> headers = new List<R8Image>();
readonly List<R8Image> frames = new List<R8Image>();
public IEnumerable<ISpriteFrame> Frames { get { return frames.Cast<ISpriteFrame>(); } }
public bool CacheWhenLoadingTileset { get { return true; } }
public readonly int Frames;
public readonly int ImageCount;
public R8Reader(Stream stream)
{
while (stream.Position < stream.Length)
{
headers.Add(new R8Image(stream));
Frames++;
frames.Add(new R8Image(stream));
ImageCount++;
}
}
public R8Image this[int index]
{
get { return headers[index]; }
}
public IEnumerator<R8Image> GetEnumerator()
{
return headers.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -0,0 +1,115 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
namespace OpenRA.FileFormats
{
enum Dune2ImageFlags : int
{
F80_F2 = 0,
F2 = 2,
L16_F80_F2_1 = 1,
L16_F80_F2_2 = 3,
Ln_F80_F2 = 5
}
class Frame : 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 Frame(Stream s)
{
var flags = (Dune2ImageFlags)s.ReadUInt16();
s.Position += 1;
var width = s.ReadUInt16();
var height = s.ReadUInt8();
Size = new Size(width, height);
var frameSize = s.ReadUInt16();
var dataSize = s.ReadUInt16();
byte[] table;
if (flags == Dune2ImageFlags.L16_F80_F2_1 ||
flags == Dune2ImageFlags.L16_F80_F2_2 ||
flags == Dune2ImageFlags.Ln_F80_F2)
{
var n = flags == Dune2ImageFlags.Ln_F80_F2 ? s.ReadUInt8() : (byte)16;
table = new byte[n];
for (var i = 0; i < n; i++)
table[i] = s.ReadUInt8();
}
else
{
table = new byte[256];
for (var i = 0; i < 256; i++)
table[i] = (byte)i;
table[1] = 0x7f;
table[2] = 0x7e;
table[3] = 0x7d;
table[4] = 0x7c;
}
// Subtract header size
var imgData = s.ReadBytes(frameSize - 10);
Data = new byte[width * height];
// Decode image data
if (flags != Dune2ImageFlags.F2)
{
var tempData = new byte[dataSize];
Format80.DecodeInto(imgData, tempData);
Format2.DecodeInto(tempData, Data);
}
else
Format2.DecodeInto(imgData, Data);
// Lookup values in lookup table
for (var j = 0; j < Data.Length; j++)
Data[j] = table[Data[j]];
}
}
public class ShpD2Reader : ISpriteSource
{
List<Frame> headers = new List<Frame>();
public IEnumerable<ISpriteFrame> Frames { get { return headers.Cast<ISpriteFrame>(); } }
public bool CacheWhenLoadingTileset { get { return false; } }
public ShpD2Reader(Stream s)
{
var imageCount = s.ReadUInt16();
// Last offset is pointer to end of file.
var offsets = new uint[imageCount + 1];
var temp = s.ReadUInt32();
// If fourth byte in file is non-zero, the offsets are two bytes each.
var twoByteOffset = (temp & 0xFF0000) > 0;
s.Position = 2;
for (var i = 0; i < imageCount + 1; i++)
offsets[i] = (twoByteOffset ? s.ReadUInt16() : s.ReadUInt32()) + 2;
for (var i = 0; i < imageCount; i++)
{
s.Position = offsets[i];
headers.Add(new Frame(s));
}
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2013 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,
@@ -15,153 +15,180 @@ using System.Linq;
namespace OpenRA.FileFormats
{
public class ImageHeader
enum Format { Format20 = 0x20, Format40 = 0x40, Format80 = 0x80 }
class ImageHeader : ISpriteFrame
{
public uint Offset;
public Size Size { get { return reader.Size; } }
public Size FrameSize { get { return reader.Size; } }
public float2 Offset { get { return float2.Zero; } }
public byte[] Data { get; set; }
public uint FileOffset;
public Format Format;
public uint RefOffset;
public Format RefFormat;
public ImageHeader RefImage;
public byte[] Image;
ShpReader reader;
// Used by ShpWriter
public ImageHeader() { }
public ImageHeader( BinaryReader reader )
public ImageHeader(Stream stream, ShpReader reader)
{
var data = reader.ReadUInt32();
Offset = data & 0xffffff;
this.reader = reader;
var data = stream.ReadUInt32();
FileOffset = data & 0xffffff;
Format = (Format)(data >> 24);
RefOffset = reader.ReadUInt16();
RefFormat = (Format)reader.ReadUInt16();
RefOffset = stream.ReadUInt16();
RefFormat = (Format)stream.ReadUInt16();
}
public static readonly int SizeOnDisk = 8;
public void WriteTo(BinaryWriter writer)
{
writer.Write(Offset | ((uint)Format << 24));
writer.Write(FileOffset | ((uint)Format << 24));
writer.Write((ushort)RefOffset);
writer.Write((ushort)RefFormat);
}
}
public enum Format { Format20 = 0x20, Format40 = 0x40, Format80 = 0x80 }
public class ShpReader
public class ShpReader : ISpriteSource
{
public readonly int ImageCount;
public readonly ushort Width;
public readonly ushort Height;
public Size Size { get { return new Size(Width, Height); } }
readonly List<ImageHeader> headers = new List<ImageHeader>();
public IEnumerable<ISpriteFrame> Frames { get { return headers.Cast<ISpriteFrame>(); } }
public bool CacheWhenLoadingTileset { get { return false; } }
public readonly Size Size;
int recurseDepth = 0;
readonly int imageCount;
public ShpReader(Stream stream)
{
using (var reader = new BinaryReader(stream))
imageCount = stream.ReadUInt16();
stream.Position += 4;
var width = stream.ReadUInt16();
var height = stream.ReadUInt16();
Size = new Size(width, height);
stream.Position += 4;
for (var i = 0; i < imageCount; i++)
headers.Add(new ImageHeader(stream, this));
// Skip eof and zero headers
stream.Position += 16;
var offsets = headers.ToDictionary(h => h.FileOffset, h => h);
for (var i = 0; i < imageCount; i++)
{
ImageCount = reader.ReadUInt16();
reader.ReadUInt16();
reader.ReadUInt16();
Width = reader.ReadUInt16();
Height = reader.ReadUInt16();
reader.ReadUInt32();
var h = headers[i];
if (h.Format == Format.Format20)
h.RefImage = headers[i - 1];
for (int i = 0 ; i < ImageCount ; i++)
headers.Add(new ImageHeader(reader));
new ImageHeader(reader); // end-of-file header
new ImageHeader(reader); // all-zeroes header
var offsets = headers.ToDictionary(h => h.Offset, h =>h);
for (int i = 0 ; i < ImageCount ; i++)
{
var h = headers[ i ];
if (h.Format == Format.Format20)
h.RefImage = headers[i - 1];
else if (h.Format == Format.Format40)
if (!offsets.TryGetValue(h.RefOffset, out h.RefImage))
throw new InvalidDataException("Reference doesnt point to image data {0}->{1}".F(h.Offset, h.RefOffset));
}
foreach (ImageHeader h in headers)
Decompress(stream, h);
else if (h.Format == Format.Format40 && !offsets.TryGetValue(h.RefOffset, out h.RefImage))
throw new InvalidDataException("Reference doesnt point to image data {0}->{1}".F(h.FileOffset, h.RefOffset));
}
foreach (var h in headers)
Decompress(stream, h);
}
public ImageHeader this[int index]
static byte[] ReadCompressedData(Stream stream, ImageHeader h)
{
get { return headers[index]; }
stream.Position = h.FileOffset;
// Actually, far too big. There's no length field with the correct length though :(
var compressedLength = (int)(stream.Length - stream.Position);
var compressedBytes = new byte[compressedLength];
stream.Read(compressedBytes, 0, compressedLength);
return compressedBytes;
}
void Decompress(Stream stream, ImageHeader h)
{
if (recurseDepth > ImageCount)
if (recurseDepth > imageCount)
throw new InvalidDataException("Format20/40 headers contain infinite loop");
switch(h.Format)
switch (h.Format)
{
case Format.Format20:
case Format.Format40:
{
if (h.RefImage.Data == null)
{
if (h.RefImage.Image == null)
{
++recurseDepth;
Decompress(stream, h.RefImage);
--recurseDepth;
}
++recurseDepth;
Decompress(stream, h.RefImage);
--recurseDepth;
}
h.Data = CopyImageData(h.RefImage.Data);
Format40.DecodeInto(ReadCompressedData(stream, h), h.Data);
break;
}
h.Image = CopyImageData(h.RefImage.Image);
Format40.DecodeInto(ReadCompressedData(stream, h), h.Image);
break;
}
case Format.Format80:
{
var imageBytes = new byte[Width * Height];
Format80.DecodeInto(ReadCompressedData(stream, h), imageBytes);
h.Image = imageBytes;
break;
}
{
var imageBytes = new byte[Size.Width * Size.Height];
Format80.DecodeInto(ReadCompressedData(stream, h), imageBytes);
h.Data = imageBytes;
break;
}
default:
throw new InvalidDataException();
}
}
static byte[] ReadCompressedData(Stream stream, ImageHeader h)
{
stream.Position = h.Offset;
// TODO: Actually, far too big. There's no length field with the correct length though :(
var compressedLength = (int)(stream.Length - stream.Position);
var compressedBytes = new byte[ compressedLength ];
stream.Read( compressedBytes, 0, compressedLength );
return compressedBytes;
}
byte[] CopyImageData(byte[] baseImage)
{
var imageData = new byte[Width * Height];
for (int i = 0 ; i < Width * Height ; i++)
var imageData = new byte[Size.Width * Size.Height];
for (var i = 0; i < Size.Width * Size.Height; i++)
imageData[i] = baseImage[i];
return imageData;
}
public IEnumerable<ImageHeader> Frames { get { return headers; } }
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)
{
var compressedFrames = frames.Select(f => Format80.Encode(f)).ToArray();
// note: end-of-file and all-zeroes headers
var dataOffset = 14 + (compressedFrames.Length + 2) * 8;
using (var bw = new BinaryWriter(s))
{
bw.Write((ushort)compressedFrames.Length);
bw.Write((ushort)0);
bw.Write((ushort)0);
bw.Write((ushort)size.Width);
bw.Write((ushort)size.Height);
bw.Write((uint)0);
foreach (var f in compressedFrames)
{
var ih = new ImageHeader { Format = Format.Format80, FileOffset = (uint)dataOffset };
dataOffset += f.Length;
ih.WriteTo(bw);
}
var eof = new ImageHeader { FileOffset = (uint)dataOffset };
eof.WriteTo(bw);
var allZeroes = new ImageHeader { };
allZeroes.WriteTo(bw);
foreach (var f in compressedFrames)
bw.Write(f);
}
}
}
}

View File

@@ -1,615 +1,112 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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 LICENSE.
* see COPYING.
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
namespace OpenRA.FileFormats
{
public struct Header
class FrameHeader : ISpriteFrame
{
public ushort A;
// Unknown
// Width and Height of the images
public ushort Width;
public ushort Height;
public ushort NumImages;
}
public Size Size { get; private set; }
public Size FrameSize { get; private set; }
public float2 Offset { get; private set; }
public byte[] Data { get; set; }
public class HeaderImage
{
public ushort x;
public ushort y;
public ushort cx;
public ushort cy;
// cx and cy are width n height of stored image
public byte compression;
public byte[] align;
public byte[] transparent;
public int zero;
public int offset;
public byte[] Image;
}
public readonly uint FileOffset;
public readonly byte Format;
public struct SHPData
{
public HeaderImage HeaderImage;
public byte[] Databuffer;
public byte[] FrameImage;
}
public struct SHP
{
public Header Header;
public SHPData[] Data;
}
public class ShpTSReader : IEnumerable<HeaderImage>
{
public readonly int ImageCount;
public readonly ushort Width;
public readonly ushort Height;
public readonly ushort Width2;
public readonly ushort Height2;
public int arroff = 0;
public int erri = 0;
public int errj = 0;
public int errk = 0;
public int errl = 0;
public static int FindNextOffsetFrom(SHP SHP, int Init, int Last)
public FrameHeader(Stream stream, Size frameSize)
{
int result;
result = 0;
Last++;
while ((result == 0) && (Init < Last))
{
result = SHP.Data[Init].HeaderImage.offset;
Init++;
}
return result;
var x = stream.ReadUInt16();
var y = stream.ReadUInt16();
var width = stream.ReadUInt16();
var height = stream.ReadUInt16();
Offset = new float2(x + 0.5f * (width - frameSize.Width), y + 0.5f * (height - frameSize.Height));
Size = new Size(width, height);
FrameSize = frameSize;
Format = stream.ReadUInt8();
stream.Position += 11;
FileOffset = stream.ReadUInt32();
}
}
private readonly List<HeaderImage> headers = new List<HeaderImage>();
public class ShpTSReader : ISpriteSource
{
readonly List<FrameHeader> frames = new List<FrameHeader>();
public IEnumerable<ISpriteFrame> Frames { get { return frames.Cast<ISpriteFrame>(); } }
public bool CacheWhenLoadingTileset { get { return false; } }
public ShpTSReader(Stream s)
public ShpTSReader(Stream stream)
{
stream.ReadUInt16();
var width = stream.ReadUInt16();
var height = stream.ReadUInt16();
var size = new Size(width, height);
var frameCount = stream.ReadUInt16();
SHP SHP = new SHP();
int FileSize;
int x;
int k = 0;
int l = 0;
for (var i = 0; i < frameCount; i++)
frames.Add(new FrameHeader(stream, size));
int ImageSize;
int NextOffset;
for (var i = 0; i < frameCount; i++)
{
var f = frames[i];
if (f.FileOffset == 0)
continue;
byte[] FData;
byte cp;
byte[] Databuffer;
stream.Position = f.FileOffset;
FileSize = (int)s.Length;
// Get Header
SHP.Header.A = s.ReadUInt16();
SHP.Header.Width = s.ReadUInt16();
SHP.Header.Height = s.ReadUInt16();
SHP.Header.NumImages = s.ReadUInt16();
// Uncompressed
if (f.Format == 1 || f.Format == 0)
f.Data = stream.ReadBytes(f.Size.Width * f.Size.Height);
SHP.Data = new SHPData[SHP.Header.NumImages + 1];
ImageCount = SHP.Header.NumImages;
for (x = 1; x <= SHP.Header.NumImages; x++)
// Uncompressed scanlines
else if (f.Format == 2)
{
SHP.Data[x].HeaderImage = new HeaderImage();
SHP.Data[x].HeaderImage.x = s.ReadUInt16();
SHP.Data[x].HeaderImage.y = s.ReadUInt16();
SHP.Data[x].HeaderImage.cx = s.ReadUInt16();
SHP.Data[x].HeaderImage.cy = s.ReadUInt16();
SHP.Data[x].HeaderImage.compression = s.ReadUInt8();
SHP.Data[x].HeaderImage.align = s.ReadBytes(3);
s.ReadInt32();
SHP.Data[x].HeaderImage.zero = s.ReadUInt8();
SHP.Data[x].HeaderImage.transparent = s.ReadBytes(3);
SHP.Data[x].HeaderImage.offset = s.ReadInt32();
}
Width = SHP.Header.Width;
Height = SHP.Header.Height;
for (int i = 0; i < ImageCount; i++)
{
headers.Add(SHP.Data[i+1].HeaderImage);
}
// Read and decode each image from the file
for (x = 1; x <= SHP.Header.NumImages; x++)
{
headers[x - 1].Image = new byte[(Width * Height)];
for (int i = 0; i < headers[x - 1].Image.Length; i++)
headers[x - 1].Image[i] = 0;
FData = new byte[(Width * Height)];
// Does it really reads the frame?
if (SHP.Data[x].HeaderImage.offset != 0)
f.Data = new byte[f.Size.Width * f.Size.Height];
for (var j = 0; j < f.Size.Height; j++)
{
try
var length = stream.ReadUInt16() - 2;
stream.Read(f.Data, f.Size.Width * j, length);
}
}
// RLE-zero compressed scanlines
else if (f.Format == 3)
{
f.Data = new byte[f.Size.Width * f.Size.Height];
for (var j = 0; j < f.Size.Height; j++)
{
var k = j * f.Size.Width;
var length = stream.ReadUInt16() - 2;
while (length > 0)
{
// Now it checks the compression:
if ((SHP.Data[x].HeaderImage.compression == 3))
{
// decode it
// Compression 3
NextOffset = FindNextOffsetFrom(SHP, x + 1, SHP.Header.NumImages);
if (NextOffset != 0)
var b = stream.ReadUInt8();
length--;
if (b == 0)
{
ImageSize = NextOffset - SHP.Data[x].HeaderImage.offset;
Databuffer = new byte[ImageSize];
for (int i = 0; i < ImageSize; i++)
{
s.Seek(SHP.Data[x].HeaderImage.offset + i, SeekOrigin.Begin);
Databuffer[i] = s.ReadUInt8();
}
SHP.Data[x].Databuffer = new byte[(SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy)];
Decode3(Databuffer, ref SHP.Data[x].Databuffer, SHP.Data[x].HeaderImage.cx, SHP.Data[x].HeaderImage.cy, ref FileSize);
k = 0;
l = 0;
for (int i = 0; i < Height; i++)
{
erri = i;
for (int j = SHP.Data[x].HeaderImage.x; j < Width; j++)
{
errj = j;
errl = l;
errk = k;
arroff = i + j + l;
if (((j + 1) > (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x)) || ((i + 1) > (SHP.Data[x].HeaderImage.cy)))
cp = 0;
else
cp = SHP.Data[x].Databuffer[i + (j - SHP.Data[x].HeaderImage.x) + l];
FData[i + j + k] = cp;
if (j == (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x - 1))
l = l + (SHP.Data[x].HeaderImage.cx - 1);
if (j == (Width - 1))
k = k + (Width - 1);
}
}
//FData = headers[x - 1].Image;
k = 0;
for (int i = 0; i < (Height - SHP.Data[x].HeaderImage.y); i++)
{
for (int j = 0; j < Width; j++)
{
headers[x - 1].Image[i + j + k + (Width * SHP.Data[x].HeaderImage.y)] = FData[i + j + k];
if (j == (Width - 1))
{
k = k + (Width - 1);
}
}
}
k += stream.ReadUInt8();
length--;
}
else
{
ImageSize = 0;
ImageSize = FileSize - SHP.Data[x].HeaderImage.offset;
Databuffer = new byte[ImageSize];
for (int i = 0; i < ImageSize; i++)
{
s.Seek(SHP.Data[x].HeaderImage.offset + i, SeekOrigin.Begin);
Databuffer[i] = s.ReadUInt8();
}
SHP.Data[x].Databuffer = new byte[((SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy))];
Decode3(Databuffer, ref SHP.Data[x].Databuffer, SHP.Data[x].HeaderImage.cx, SHP.Data[x].HeaderImage.cy, ref ImageSize);
k = 0;
l = 0;
for (int i = 0; i < Height; i++)
{
erri = i;
for (int j = SHP.Data[x].HeaderImage.x; j < Width; j++)
{
errj = j;
errl = l;
errk = k;
arroff = i + j + l;
if (((j + 1) > (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x)) || ((i + 1) > (SHP.Data[x].HeaderImage.cy)))
cp = 0;
else
cp = SHP.Data[x].Databuffer[i + (j - SHP.Data[x].HeaderImage.x) + l];
FData[i + j + k] = cp;
if (j == (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x - 1))
l = l + (SHP.Data[x].HeaderImage.cx - 1);
if (j == (Width - 1))
k = k + (Width - 1);
}
}
//FData = headers[x - 1].Image;
k = 0;
for (int i = 0; i < (Height - SHP.Data[x].HeaderImage.y); i++)
{
for (int j = 0; j < Width; j++)
{
headers[x - 1].Image[i + j + k + (Width * SHP.Data[x].HeaderImage.y)] = FData[i + j + k];
if (j == (Width - 1))
{
k = k + (Width - 1);
}
}
}
}
f.Data[k++] = b;
}
else if ((SHP.Data[x].HeaderImage.compression == 2))
{
NextOffset = FindNextOffsetFrom(SHP, x + 1, SHP.Header.NumImages);
if (NextOffset != 0)
{
ImageSize = NextOffset - SHP.Data[x].HeaderImage.offset;
SHP.Data[x].Databuffer = new byte[(SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy)];
Databuffer = new byte[ImageSize];
for (int i = 0; i < ImageSize; i++)
{
s.Seek(SHP.Data[x].HeaderImage.offset + i, SeekOrigin.Begin);
Databuffer[i] = s.ReadUInt8();
}
Decode2(Databuffer, ref SHP.Data[x].Databuffer, SHP.Data[x].HeaderImage.cx, SHP.Data[x].HeaderImage.cy, ref ImageSize);
k = 0;
l = 0;
for (int i = 0; i < Height; i++)
{
erri = i;
for (int j = SHP.Data[x].HeaderImage.x; j < Width; j++)
{
errj = j;
errl = l;
errk = k;
arroff = i + j + l;
if (((j + 1) > (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x)) || ((i + 1) > (SHP.Data[x].HeaderImage.cy)))
cp = 0;
else
cp = SHP.Data[x].Databuffer[i + (j - SHP.Data[x].HeaderImage.x) + l];
FData[i + j + k] = cp;
if (j == (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x - 1))
l = l + (SHP.Data[x].HeaderImage.cx - 1);
if (j == (Width - 1))
k = k + (Width - 1);
}
}
//FData = headers[x - 1].Image;
k = 0;
for (int i = 0; i < (Height - SHP.Data[x].HeaderImage.y); i++)
{
for (int j = 0; j < Width; j++)
{
headers[x - 1].Image[i + j + k + (Width * SHP.Data[x].HeaderImage.y)] = FData[i + j + k];
if (j == (Width - 1))
{
k = k + (Width - 1);
}
}
}
// Compression 2
}
else
{
ImageSize = 0;
ImageSize = FileSize - SHP.Data[x].HeaderImage.offset;
Databuffer = new byte[ImageSize];
for (int i = 0; i < ImageSize; i++)
{
s.Seek(SHP.Data[x].HeaderImage.offset + i, SeekOrigin.Begin);
Databuffer[i] = s.ReadUInt8();
}
SHP.Data[x].Databuffer = new byte[((SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy))];
Decode2(Databuffer, ref SHP.Data[x].Databuffer, SHP.Data[x].HeaderImage.cx, SHP.Data[x].HeaderImage.cy, ref ImageSize);
k = 0;
l = 0;
for (int i = 0; i < Height; i++)
{
erri = i;
for (int j = SHP.Data[x].HeaderImage.x; j < Width; j++)
{
errj = j;
errl = l;
errk = k;
arroff = i + j + l;
if (((j + 1) > (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x)) || ((i + 1) > (SHP.Data[x].HeaderImage.cy)))
cp = 0;
else
cp = SHP.Data[x].Databuffer[i + (j - SHP.Data[x].HeaderImage.x) + l];
FData[i + j + k] = cp;
if (j == (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x - 1))
l = l + (SHP.Data[x].HeaderImage.cx - 1);
if (j == (Width - 1))
k = k + (Width - 1);
}
}
//FData = headers[x - 1].Image;
k = 0;
for (int i = 0; i < (Height - SHP.Data[x].HeaderImage.y); i++)
{
for (int j = 0; j < Width; j++)
{
headers[x - 1].Image[i + j + k + (Width * SHP.Data[x].HeaderImage.y)] = FData[i + j + k];
if (j == (Width - 1))
{
k = k + (Width - 1);
}
}
}
// Compression 2
}
}
else
{
// Compression 1
ImageSize = (int)(SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy);
Databuffer = new byte[ImageSize];
for (int i = 0; i < ImageSize; i++)
{
s.Seek(SHP.Data[x].HeaderImage.offset + i, SeekOrigin.Begin);
Databuffer[i] = s.ReadUInt8();
}
SHP.Data[x].Databuffer = new byte[(SHP.Data[x].HeaderImage.cx * SHP.Data[x].HeaderImage.cy)];
SHP.Data[x].Databuffer = Databuffer;
k = 0;
l = 0;
for (int i = 0; i < Height; i++)
{
erri = i;
for (int j = SHP.Data[x].HeaderImage.x; j < Width; j++)
{
errj = j;
errl = l;
errk = k;
arroff = i + j + l;
if (((j + 1) > (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x)) || ((i + 1) > (SHP.Data[x].HeaderImage.cy)))
cp = 0;
else
cp = SHP.Data[x].Databuffer[i + (j - SHP.Data[x].HeaderImage.x) + l];
FData[i + j + k] = cp;
if (j == (SHP.Data[x].HeaderImage.cx + SHP.Data[x].HeaderImage.x - 1))
l = l + (SHP.Data[x].HeaderImage.cx - 1);
if (j == (Width - 1))
k = k + (Width - 1);
}
}
//FData = headers[x - 1].Image;
k = 0;
for (int i = 0; i < (Height - SHP.Data[x].HeaderImage.y); i++)
{
for (int j = 0; j < Width; j++)
{
headers[x - 1].Image[i + j + k + (Width * SHP.Data[x].HeaderImage.y)] = FData[i + j + k];
if (j == (Width - 1))
{
k = k + (Width - 1);
}
}
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
// Set the shp's databuffer to the result after decompression
}
//Width = Width2;
//Height = Height2;
}
public HeaderImage this[int index]
{
get { return headers[index]; }
}
public static void ReInterpretWordFromBytes(byte Byte1, byte Byte2, ref ushort FullValue)
{
FullValue = (ushort)((Byte2 * 256) + Byte1);
}
public static void ReInterpretWordFromBytes(byte Byte1, byte Byte2, ref uint FullValue)
{
FullValue = (uint)((Byte2 * 256) + Byte1);
}
// Compression 3:
public static void Decode3(byte[] Source, ref byte[] Dest, int cx, int cy, ref int max)
{
int SP;
int DP;
int x;
int y;
int Count;
int v;
int maxdp;
ushort Pos;
maxdp = cx * cy;
SP = 0;
DP = 0;
Pos = 0;
try
{
for (y = 1; y <= cy; y++)
{
ReInterpretWordFromBytes(Source[SP], Source[SP + 1], ref Pos);
Count = Pos - 2;
SP = SP + 2;
x = 0;
while (Count > 0)
{
Count = Count - 1;
if ((SP > max) || (DP > maxdp))
{
break;
}
else
{
// SP has reached max value, exit
v = Source[SP];
SP++;
if (v != 0)
{
if ((SP > max) || (DP > maxdp))
{
break;
}
else
{
x++;
Dest[DP] += (byte)v;
}
DP++;
}
else
{
Count -= 1;
v = Source[SP];
SP++;
if ((x + v) > cx)
{
v = cx - x;
}
x = x + v;
while (v > 0)
{
if ((SP > max) || (DP > maxdp))
{
break;
}
else
{
v -= 1;
Dest[DP] = 0;
}
DP++;
// SP has reached max value, exit
}
}
}
}
if ((SP >= max) || (DP >= maxdp))
{
return;
}
// SP has reached max value, exit
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public static void Decode2(byte[] Source, ref byte[] Dest, int cx, int cy, ref int max)
{
int SP;
int DP;
int y;
int Count;
int maxdp;
ushort Pos;
maxdp = cx * cy;
SP = 0;
DP = 0;
Pos = 0;
try
{
for (y = 1; y <= cy; y++)
{
ReInterpretWordFromBytes(Source[SP], Source[SP + 1], ref Pos);
Count = Pos - 2;
SP += 2;
while (Count > 0)
{
Count -= 1;
if ((SP > max) || (DP > maxdp))
{
return;
}
// SP has reached max value, exit
Dest[DP] = Source[SP];
SP++;
DP++;
}
if ((SP >= max) || (DP >= maxdp))
{
return;
}
// SP has reached max value, exit
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public IEnumerator<HeaderImage> GetEnumerator()
{
return headers.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Size Size { get { return new Size(Width, Height); } }
}
}
}

View File

@@ -1,56 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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.IO;
using System.Linq;
namespace OpenRA.FileFormats.Graphics
{
// format80-only SHP writer
public static class ShpWriter
{
public static void Write(Stream s, int width, int height, IEnumerable<byte[]> frames)
{
var compressedFrames = frames.Select(f => Format80.Encode(f)).ToArray();
// note: end-of-file and all-zeroes headers
var dataOffset = 14 + (compressedFrames.Length + 2) * ImageHeader.SizeOnDisk;
using (var bw = new BinaryWriter(s))
{
bw.Write((ushort)compressedFrames.Length);
bw.Write((ushort)0); // unused
bw.Write((ushort)0); // unused
bw.Write((ushort)width);
bw.Write((ushort)height);
bw.Write((uint)0); // unused
foreach (var f in compressedFrames)
{
var ih = new ImageHeader { Format = Format.Format80, Offset = (uint)dataOffset };
dataOffset += f.Length;
ih.WriteTo(bw);
}
var eof = new ImageHeader { Offset = (uint)dataOffset };
eof.WriteTo(bw);
var allZeroes = new ImageHeader { };
allZeroes.WriteTo(bw);
foreach (var f in compressedFrames)
bw.Write(f);
}
}
}
}

View File

@@ -0,0 +1,236 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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 System.Linq;
namespace OpenRA.FileFormats
{
public interface ISpriteFrame
{
Size Size { get; }
Size FrameSize { get; }
float2 Offset { get; }
byte[] Data { get; }
}
public interface ISpriteSource
{
IEnumerable<ISpriteFrame> Frames { get; }
bool CacheWhenLoadingTileset { get; }
}
public enum SpriteType { Unknown, ShpTD, ShpTS, ShpD2, TmpTD, TmpRA, 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 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 (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.ShpD2:
return new ShpD2Reader(s);
case SpriteType.Unknown:
default:
throw new InvalidDataException(filename + " is not a valid sprite file");
}
}
}
}

View File

@@ -0,0 +1,50 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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 System.Linq;
namespace OpenRA.FileFormats
{
public class TmpRAReader : ISpriteSource
{
readonly List<TmpTile> tiles = new List<TmpTile>();
public IEnumerable<ISpriteFrame> Frames { get { return tiles.Cast<ISpriteFrame>(); } }
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;
foreach (byte b in s.ReadBytes(indexEnd - indexStart))
{
if (b != 255)
{
s.Position = imgStart + b * width * height;
tiles.Add(new TmpTile(s.ReadBytes(width * height), size));
}
else
tiles.Add(new TmpTile(null, size));
}
}
}
}

View File

@@ -0,0 +1,68 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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.Linq;
using System.IO;
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
{
readonly List<TmpTile> tiles = new List<TmpTile>();
public IEnumerable<ISpriteFrame> Frames { get { return tiles.Cast<ISpriteFrame>(); } }
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;
foreach (byte b in s.ReadBytes(indexEnd - indexStart))
{
if (b != 255)
{
s.Position = imgStart + b * width * height;
tiles.Add(new TmpTile(s.ReadBytes(width * height), size));
}
else
tiles.Add(new TmpTile(null, size));
}
}
}
}

View File

@@ -1,77 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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.IO;
namespace OpenRA.FileFormats
{
public class Terrain
{
public readonly List<byte[]> TileBitmapBytes = new List<byte[]>();
public readonly int Width;
public readonly int Height;
public Terrain(Stream s)
{
// Try loading as a cnc .tem
Width = s.ReadUInt16();
Height = s.ReadUInt16();
/*NumTiles = */s.ReadUInt16();
/*Zero1 = */s.ReadUInt16();
/*uint Size = */s.ReadUInt32();
var imgStart = s.ReadUInt32();
/*Zero2 = */s.ReadUInt32();
int indexEnd, indexStart;
// ID1 = FFFFh for cnc
if (s.ReadUInt16() == 65535)
{
/*ID2 = */s.ReadUInt16();
indexEnd = s.ReadInt32();
indexStart = s.ReadInt32();
}
else
{
// Load as a ra .tem
s.Position = 0;
Width = s.ReadUInt16();
Height = s.ReadUInt16();
/*NumTiles = */s.ReadUInt16();
s.ReadUInt16();
/*XDim = */s.ReadUInt16();
/*YDim = */s.ReadUInt16();
/*uint FileSize = */s.ReadUInt32();
imgStart = s.ReadUInt32();
s.ReadUInt32();
s.ReadUInt32();
indexEnd = s.ReadInt32();
s.ReadUInt32();
indexStart = s.ReadInt32();
}
s.Position = indexStart;
foreach (byte b in s.ReadBytes(indexEnd - indexStart))
{
if (b != 255)
{
s.Position = imgStart + b * Width * Height;
TileBitmapBytes.Add(s.ReadBytes(Width * Height));
}
else
TileBitmapBytes.Add(null);
}
}
}
}

View File

@@ -90,19 +90,16 @@
<Compile Include="FileFormats\Format40.cs" />
<Compile Include="FileFormats\Format80.cs" />
<Compile Include="FileFormats\IniFile.cs" />
<Compile Include="Graphics\Dune2ShpReader.cs" />
<Compile Include="Graphics\IGraphicsDevice.cs" />
<Compile Include="Graphics\IInputHandler.cs" />
<Compile Include="Graphics\PngLoader.cs" />
<Compile Include="Graphics\ShpReader.cs" />
<Compile Include="Graphics\ShpWriter.cs" />
<Compile Include="Graphics\Vertex.cs" />
<Compile Include="Graphics\VqaReader.cs" />
<Compile Include="InstallUtils.cs" />
<Compile Include="Manifest.cs" />
<Compile Include="Map\PlayerReference.cs" />
<Compile Include="Map\SmudgeReference.cs" />
<Compile Include="Map\Terrain.cs" />
<Compile Include="Map\TileReference.cs" />
<Compile Include="Map\TileSet.cs" />
<Compile Include="MiniYaml.cs" />
@@ -144,7 +141,6 @@
<Compile Include="StreamExts.cs" />
<Compile Include="FileFormats\WavLoader.cs" />
<Compile Include="Graphics\R8Reader.cs" />
<Compile Include="Graphics\TileSetRenderer.cs" />
<Compile Include="Keycode.cs" />
<Compile Include="Hotkey.cs" />
<Compile Include="FileSystem\FileSystem.cs" />
@@ -153,6 +149,10 @@
<Compile Include="FileSystem\MixFile.cs" />
<Compile Include="FileSystem\ZipFile.cs" />
<Compile Include="FileSystem\D2kSoundResources.cs" />
<Compile Include="Graphics\SpriteSource.cs" />
<Compile Include="Graphics\TmpRAReader.cs" />
<Compile Include="Graphics\TmpTDReader.cs" />
<Compile Include="Graphics\ShpD2Reader.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">

View File

@@ -10,6 +10,7 @@
using System;
using System.Drawing;
using OpenRA.FileFormats;
using OpenRA.FileFormats.Graphics;
namespace OpenRA.Graphics
@@ -52,9 +53,14 @@ namespace OpenRA.Graphics
this.allocateSheet = allocateSheet;
}
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Size, frame.Offset); }
public Sprite Add(byte[] src, Size size) { return Add(src, size, float2.Zero); }
public Sprite Add(byte[] src, Size size, float2 spriteOffset)
{
// Don't bother allocating empty sprites
if (size.Width == 0 || size.Height == 0)
return new Sprite(current, Rectangle.Empty, spriteOffset, channel, BlendMode.Alpha);
var rect = Allocate(size, spriteOffset);
Util.FastCopyIntoChannel(rect, src);
current.CommitData();

View File

@@ -16,39 +16,25 @@ namespace OpenRA.Graphics
{
public class SpriteLoader
{
public SpriteLoader(string[] exts, SheetBuilder sheetBuilder)
{
SheetBuilder = sheetBuilder;
this.exts = exts;
sprites = new Cache<string, Sprite[]>(LoadSprites);
}
readonly SheetBuilder SheetBuilder;
readonly Cache<string, Sprite[]> sprites;
readonly string[] exts;
Sprite[] LoadSprites(string filename)
public SpriteLoader(string[] exts, SheetBuilder sheetBuilder)
{
// TODO: Cleanly abstract file type detection
if (filename.ToLower().EndsWith("r8"))
{
var r8 = new R8Reader(FileSystem.Open(filename));
return r8.Select(a => SheetBuilder.Add(a.Image, a.Size, a.Offset)).ToArray();
}
SheetBuilder = sheetBuilder;
BinaryReader reader = new BinaryReader(FileSystem.OpenWithExts(filename, exts));
// Include extension-less version
this.exts = exts.Append("").ToArray();
sprites = new Cache<string, Sprite[]>(CacheSpriteFrames);
}
var ImageCount = reader.ReadUInt16();
if (ImageCount == 0)
{
var shp = new ShpTSReader(FileSystem.OpenWithExts(filename, exts));
return shp.Select(a => SheetBuilder.Add(a.Image, shp.Size)).ToArray();
}
else
{
var shp = new ShpReader(FileSystem.OpenWithExts(filename, exts));
return shp.Frames.Select(a => SheetBuilder.Add(a.Image, shp.Size)).ToArray();
}
Sprite[] CacheSpriteFrames(string filename)
{
var stream = FileSystem.OpenWithExts(filename, exts);
return SpriteSource.LoadSpriteSource(stream, filename).Frames
.Select(a => SheetBuilder.Add(a))
.ToArray();
}
public Sprite[] LoadAllSprites(string filename) { return sprites[filename]; }

View File

@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
@@ -22,27 +23,31 @@ namespace OpenRA.Graphics
Dictionary<ushort, Sprite[]> templates;
Sprite missingTile;
Sprite[] LoadTemplate(string filename, string[] exts, Cache<string, R8Reader> r8Cache, int[] frames)
Sprite[] LoadTemplate(string filename, string[] exts, Dictionary<string, ISpriteSource> sourceCache, int[] frames)
{
if (exts.Contains(".R8") && FileSystem.Exists(filename+".R8"))
ISpriteSource source;
if (!sourceCache.ContainsKey(filename))
{
return frames.Select(f =>
{
if (f < 0)
return null;
using (var s = FileSystem.OpenWithExts(filename, exts))
source = SpriteSource.LoadSpriteSource(s, filename);
var image = r8Cache[filename][f];
return sheetBuilder.Add(image.Image, new Size(image.Size.Width, image.Size.Height));
}).ToArray();
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();
}
using (var s = FileSystem.OpenWithExts(filename, exts))
{
var t = new Terrain(s);
return t.TileBitmapBytes
.Select(b => b != null ? sheetBuilder.Add(b, new Size(t.Width, t.Height)) : null)
.ToArray();
}
return source.Frames.Select(f => sheetBuilder.Add(f)).ToArray();
}
public Theater(TileSet tileset)
@@ -57,11 +62,11 @@ namespace OpenRA.Graphics
return new Sheet(new Size(tileset.SheetSize, tileset.SheetSize));
};
var r8Cache = new Cache<string, R8Reader>(s => new R8Reader(FileSystem.OpenWithExts(s, ".R8")));
var sourceCache = new Dictionary<string, ISpriteSource>();
templates = new Dictionary<ushort, Sprite[]>();
sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate);
foreach (var t in tileset.Templates)
templates.Add(t.Value.Id, LoadTemplate(t.Value.Image, tileset.Extensions, r8Cache, t.Value.Frames));
templates.Add(t.Value.Id, LoadTemplate(t.Value.Image, tileset.Extensions, sourceCache, t.Value.Frames));
// 1x1px transparent tile
missingTile = sheetBuilder.Add(new byte[1], new Size(1, 1));
@@ -70,11 +75,13 @@ namespace OpenRA.Graphics
public Sprite TileSprite(TileReference<ushort, byte> r)
{
Sprite[] template;
if (templates.TryGetValue(r.Type, out template))
if (template.Length > r.Index && template[r.Index] != null)
return template[r.Index];
if (!templates.TryGetValue(r.Type, out template))
return missingTile;
return missingTile;
if (r.Index >= template.Length)
return missingTile;
return template[r.Index];
}
public Sheet Sheet { get { return sheetBuilder.Current; } }

View File

@@ -75,7 +75,7 @@ namespace OpenRA
ChromeMetrics.Initialize(Manifest.ChromeMetrics);
ChromeProvider.Initialize(Manifest.Chrome);
SheetBuilder = new SheetBuilder(SheetType.Indexed);
SpriteLoader = new SpriteLoader(new string[] { ".shp" }, SheetBuilder);
SpriteLoader = new SpriteLoader(new string[0], SheetBuilder);
VoxelLoader = new VoxelLoader();
CursorProvider.Initialize(Manifest.Cursors);
}

View File

@@ -56,6 +56,7 @@ namespace OpenRA.Widgets
Sprite sprite = null;
string cachedImage = null;
int cachedFrame = -1;
float2 cachedOffset = float2.Zero;
public override void Draw()
{
@@ -68,9 +69,10 @@ namespace OpenRA.Widgets
sprite = Game.modData.SpriteLoader.LoadAllSprites(image)[frame];
cachedImage = image;
cachedFrame = frame;
cachedOffset = 0.5f * (new float2(RenderBounds.Size) - sprite.size);
}
Game.Renderer.SpriteRenderer.DrawSprite(sprite, RenderOrigin, worldRenderer.Palette(palette));
Game.Renderer.SpriteRenderer.DrawSprite(sprite, RenderOrigin + cachedOffset, worldRenderer.Palette(palette));
}
public int FrameCount

View File

@@ -29,14 +29,14 @@ namespace OpenRA.Widgets
Game.Renderer.RgbaSpriteRenderer.DrawSprite(s,pos);
}
public static void DrawSHP(Sprite s, float2 pos, WorldRenderer wr)
public static void DrawSHPCentered(Sprite s, float2 pos, WorldRenderer wr)
{
Game.Renderer.SpriteRenderer.DrawSprite(s, pos, wr.Palette("chrome"));
Game.Renderer.SpriteRenderer.DrawSprite(s, pos - 0.5f * s.size, wr.Palette("chrome"));
}
public static void DrawSHP(Sprite s, float2 pos, WorldRenderer wr, float2 size)
public static void DrawSHPCentered(Sprite s, float2 pos, WorldRenderer wr, float scale)
{
Game.Renderer.SpriteRenderer.DrawSprite(s, pos, wr.Palette("chrome"), size);
Game.Renderer.SpriteRenderer.DrawSprite(s, pos - 0.5f * scale * s.size, wr.Palette("chrome"), scale * s.size);
}
public static void DrawPanel(string collection, Rectangle Bounds)

View File

@@ -197,11 +197,14 @@ namespace OpenRA.Mods.Cnc.Widgets
public override void Draw()
{
var iconSize = new float2(64, 48);
var iconOffset = 0.5f * iconSize;
overlayFont = Game.Renderer.Fonts["TinyBold"];
timeOffset = new float2(32, 24) - overlayFont.Measure(WidgetUtils.FormatTime(0)) / 2;
timeOffset = iconOffset - overlayFont.Measure(WidgetUtils.FormatTime(0)) / 2;
queuedOffset = new float2(4, 2);
holdOffset = new float2(32, 24) - overlayFont.Measure(HoldText) / 2;
readyOffset = new float2(32, 24) - overlayFont.Measure(ReadyText) / 2;
holdOffset = iconOffset - overlayFont.Measure(HoldText) / 2;
readyOffset = iconOffset - overlayFont.Measure(ReadyText) / 2;
if (CurrentQueue == null)
return;
@@ -215,7 +218,7 @@ namespace OpenRA.Mods.Cnc.Widgets
// Icons
foreach (var icon in icons.Values)
{
WidgetUtils.DrawSHP(icon.Sprite, icon.Pos, worldRenderer);
WidgetUtils.DrawSHPCentered(icon.Sprite, icon.Pos + iconOffset, worldRenderer);
// Build progress
if (icon.Queued.Count > 0)
@@ -225,10 +228,10 @@ namespace OpenRA.Mods.Cnc.Widgets
() => (first.TotalTime - first.RemainingTime)
* (clock.CurrentSequence.Length - 1) / first.TotalTime);
clock.Tick();
WidgetUtils.DrawSHP(clock.Image, icon.Pos, worldRenderer);
WidgetUtils.DrawSHPCentered(clock.Image, icon.Pos + iconOffset, worldRenderer);
}
else if (!buildableItems.Any(a => a.Name == icon.Name))
WidgetUtils.DrawSHP(cantBuild.Image, icon.Pos, worldRenderer);
WidgetUtils.DrawSHPCentered(cantBuild.Image, icon.Pos + iconOffset, worldRenderer);
}
// Overlays

View File

@@ -89,10 +89,13 @@ namespace OpenRA.Mods.Cnc.Widgets
public override void Draw()
{
var iconSize = new float2(64, 48);
var iconOffset = 0.5f * iconSize;
overlayFont = Game.Renderer.Fonts["TinyBold"];
holdOffset = new float2(32, 24) - overlayFont.Measure(HoldText) / 2;
readyOffset = new float2(32, 24) - overlayFont.Measure(ReadyText) / 2;
timeOffset = new float2(32, 24) - overlayFont.Measure(WidgetUtils.FormatTime(0)) / 2;
holdOffset = iconOffset - overlayFont.Measure(HoldText) / 2;
readyOffset = iconOffset - overlayFont.Measure(ReadyText) / 2;
timeOffset = iconOffset - overlayFont.Measure(WidgetUtils.FormatTime(0)) / 2;
// Background
foreach (var rect in icons.Keys)
@@ -101,14 +104,14 @@ namespace OpenRA.Mods.Cnc.Widgets
// Icons
foreach (var p in icons.Values)
{
WidgetUtils.DrawSHP(p.Sprite, p.Pos, worldRenderer);
WidgetUtils.DrawSHPCentered(p.Sprite, p.Pos + iconOffset, worldRenderer);
// Charge progress
clock.PlayFetchIndex("idle",
() => (p.Power.TotalTime - p.Power.RemainingTime)
* (clock.CurrentSequence.Length - 1) / p.Power.TotalTime);
clock.Tick();
WidgetUtils.DrawSHP(clock.Image, p.Pos, worldRenderer);
WidgetUtils.DrawSHPCentered(clock.Image, p.Pos + iconOffset, worldRenderer);
}
// Overlay

View File

@@ -33,9 +33,9 @@
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugSymbols>True</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Optimize>False</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
@@ -47,17 +47,17 @@
</CustomCommands>
</CustomCommands>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Optimize>True</Optimize>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<OutputPath>bin\Release\</OutputPath>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="FuzzyLogicLibrary">
@@ -70,14 +70,13 @@
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\thirdparty\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="Mono.Nat">
<HintPath>..\thirdparty\Mono.Nat.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\thirdparty\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Activities\CaptureActor.cs" />
@@ -444,7 +443,6 @@
<Compile Include="Widgets\HueSliderWidget.cs" />
<Compile Include="Render\WithTurret.cs" />
<Compile Include="Widgets\Logic\AssetBrowserLogic.cs" />
<Compile Include="Widgets\Logic\ConvertGameFilesLogic.cs" />
<Compile Include="VoxelNormalsPalette.cs" />
<Compile Include="Render\RenderVoxels.cs" />
<Compile Include="Render\WithVoxelTurret.cs" />
@@ -481,7 +479,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LuaInterface\LuaInterface.csproj">
<Project>{e915a0a4-2641-4f7e-8a88-8f123fa88bf1}</Project>
<Project>{E915A0A4-2641-4F7E-8A88-8F123FA88BF1}</Project>
<Name>LuaInterface</Name>
</ProjectReference>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">
@@ -495,13 +493,9 @@
<Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\OpenRA.Irc\OpenRA.Irc.csproj">
<Project>{85b48234-8b31-4be6-af9c-665cc6866841}</Project>
<Project>{85B48234-8B31-4BE6-AF9C-665CC6866841}</Project>
<Name>OpenRA.Irc</Name>
</ProjectReference>
<ProjectReference Include="..\OpenRA.Utility\OpenRA.Utility.csproj">
<Project>{F33337BE-CB69-4B24-850F-07D23E408DDF}</Project>
<Name>OpenRA.Utility</Name>
</ProjectReference>
<ProjectReference Include="..\GeoIP\GeoIP.csproj">
<Project>{021DDD6A-A608-424C-9A9A-252D8A9989E0}</Project>
<Name>GeoIP</Name>

View File

@@ -203,6 +203,7 @@ namespace OpenRA.Mods.RA.Widgets
string paletteCollection = "palette-" + world.LocalPlayer.Country.Race;
float2 origin = new float2(paletteOrigin.X + 9, paletteOrigin.Y + 9);
var iconOffset = 0.5f * new float2(IconWidth, IconHeight);
var x = 0;
var y = 0;
@@ -233,7 +234,7 @@ namespace OpenRA.Mods.RA.Widgets
var drawPos = new float2(rect.Location);
var icon = new Animation(RenderSimple.GetImage(item));
icon.Play(item.Traits.Get<TooltipInfo>().Icon);
WidgetUtils.DrawSHP(icon.Image, drawPos, worldRenderer);
WidgetUtils.DrawSHPCentered(icon.Image, drawPos + iconOffset, worldRenderer);
var firstOfThis = queue.AllQueued().FirstOrDefault(a => a.Item == item.Name);
@@ -248,7 +249,7 @@ namespace OpenRA.Mods.RA.Widgets
() => (firstOfThis.TotalTime - firstOfThis.RemainingTime)
* (clock.CurrentSequence.Length - 1) / firstOfThis.TotalTime);
clock.Tick();
WidgetUtils.DrawSHP(clock.Image, drawPos, worldRenderer);
WidgetUtils.DrawSHPCentered(clock.Image, drawPos + iconOffset, worldRenderer);
if (queue.CurrentItem() == firstOfThis)
textBits.Add(Pair.New(overlayPos, GetOverlayForItem(firstOfThis)));
@@ -269,7 +270,7 @@ namespace OpenRA.Mods.RA.Widgets
if (x != 0) y++;
foreach (var ob in overlayBits)
WidgetUtils.DrawSHP(ob.First, ob.Second, worldRenderer);
WidgetUtils.DrawSHPCentered(ob.First, ob.Second + iconOffset, worldRenderer);
var font = Game.Renderer.Fonts["TinyBold"];
foreach (var tb in textBits)

View File

@@ -35,6 +35,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic
PaletteFromFile currentPalette;
static readonly string[] AllowedExtensions = { ".shp", ".r8", ".tem", ".des", ".sno", ".int" };
[ObjectCreator.UseCtor]
public AssetBrowserLogic(Widget widget, Action onExit, World world)
{
@@ -60,6 +62,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var paletteDropDown = panel.Get<DropDownButtonWidget>("PALETTE_SELECTOR");
paletteDropDown.OnMouseDown = _ => ShowPaletteDropdown(paletteDropDown, world);
paletteDropDown.GetText = () => currentPalette.Name;
var colorPreview = panel.Get<ColorPreviewManagerWidget>("COLOR_MANAGER");
colorPreview.Color = Game.Settings.Player.Color;
@@ -80,7 +83,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
frameSlider.OnChange += x => { spriteWidget.Frame = (int)Math.Round(x); };
frameSlider.GetValue = () => spriteWidget.Frame;
panel.Get<LabelWidget>("FRAME_COUNT").GetText = () => "{0}/{1}".F(spriteWidget.Frame, spriteWidget.FrameCount);
panel.Get<LabelWidget>("FRAME_COUNT").GetText = () => "{0} / {1}".F(spriteWidget.Frame + 1, spriteWidget.FrameCount + 1);
playButton = panel.Get<ButtonWidget>("BUTTON_PLAY");
playButton.OnClick = () =>
@@ -118,96 +121,15 @@ namespace OpenRA.Mods.RA.Widgets.Logic
template = panel.Get<ScrollItemWidget>("ASSET_TEMPLATE");
PopulateAssetList();
var modID = Game.modData.Manifest.Mod.Id;
// TODO: This should not invoke the OpenRA.Utility.exe, but use it's functions directly.
// TODO: Does not work with SHP(TS) yet?!
panel.Get<ButtonWidget>("EXPORT_BUTTON").OnClick = () =>
{
var ExtractGameFiles = new string[][]
{
new string[] { "--extract", modID, currentPalette.Filename, "--userdir" },
new string[] { "--extract", modID, "{0}.shp".F(spriteWidget.Image), "--userdir" },
};
var ExportToPng = new string[][]
{
new string[] { "--png", Platform.SupportDir + "{0}.shp".F(spriteWidget.Image), Platform.SupportDir + currentPalette.Filename },
};
var ImportFromPng = new string[][] { };
var args = new WidgetArgs()
{
{ "ExtractGameFiles", ExtractGameFiles },
{ "ExportToPng", ExportToPng },
{ "ImportFromPng", ImportFromPng }
};
Ui.OpenWindow("CONVERT_ASSETS_PANEL", args);
};
panel.Get<ButtonWidget>("EXTRACT_BUTTON").OnClick = () =>
{
var ExtractGameFilesList = new List<string[]>();
var ExportToPngList = new List<string[]>();
ExtractGameFilesList.Add(new string[] { "--extract", modID, currentPalette.Filename, "--userdir" });
foreach (var shp in availableShps)
{
ExtractGameFilesList.Add(new string[] { "--extract", modID, shp, "--userdir" });
ExportToPngList.Add(new string[] { "--png", Platform.SupportDir + shp, Platform.SupportDir + currentPalette.Filename });
Console.WriteLine(Platform.SupportDir + shp);
}
var ExtractGameFiles = ExtractGameFilesList.ToArray();
var ExportToPng = ExportToPngList.ToArray();
var ImportFromPng = new string[][] { };
var args = new WidgetArgs()
{
{ "ExtractGameFiles", ExtractGameFiles },
{ "ExportToPng", ExportToPng },
{ "ImportFromPng", ImportFromPng }
};
Ui.OpenWindow("CONVERT_ASSETS_PANEL", args);
};
panel.Get<ButtonWidget>("IMPORT_BUTTON").OnClick = () =>
{
var imageSizeInput = panel.Get<TextFieldWidget>("IMAGE_SIZE_INPUT");
var imageFilename = panel.Get<TextFieldWidget>("IMAGE_FILENAME_INPUT");
var ExtractGameFiles = new string[][] { };
var ExportToPng = new string[][] { };
var ImportFromPng = new string[][]
{
new string[] { "--shp", Platform.SupportDir + imageFilename.Text, imageSizeInput.Text },
};
var args = new WidgetArgs()
{
{ "ExtractGameFiles", ExtractGameFiles },
{ "ExportToPng", ExportToPng },
{ "ImportFromPng", ImportFromPng }
};
Ui.OpenWindow("CONVERT_ASSETS_PANEL", args);
};
panel.Get<ButtonWidget>("CLOSE_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
}
void AddAsset(ScrollPanelWidget list, string filepath, ScrollItemWidget template)
{
var r8 = filepath.EndsWith(".r8", true, CultureInfo.InvariantCulture);
var filename = Path.GetFileName(filepath);
var sprite = r8 ? filename : Path.GetFileNameWithoutExtension(filepath);
var filename = Path.GetFileName(filepath);
var item = ScrollItemWidget.Setup(template,
() => spriteWidget.Image == sprite,
() => {filenameInput.Text = filename; LoadAsset(filename); });
() => spriteWidget.Image == filename,
() => { filenameInput.Text = filename; LoadAsset(filename); });
item.Get<LabelWidget>("TITLE").GetText = () => filepath;
list.AddChild(item);
@@ -221,11 +143,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic
if (!FileSystem.Exists(filename))
return false;
var r8 = filename.EndsWith(".r8", true, CultureInfo.InvariantCulture);
var sprite = r8 ? filename : Path.GetFileNameWithoutExtension(filename);
spriteWidget.Frame = 0;
spriteWidget.Image = sprite;
spriteWidget.Image = filename;
frameSlider.MaximumValue = (float)spriteWidget.FrameCount;
frameSlider.Ticks = spriteWidget.FrameCount + 1;
return true;
@@ -265,7 +184,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var files = assetSource.AllFileNames();
foreach (var file in files)
{
if (file.EndsWith(".shp", true, CultureInfo.InvariantCulture) || file.EndsWith(".r8", true, CultureInfo.InvariantCulture))
if (AllowedExtensions.Any(ext => file.EndsWith(ext, true, CultureInfo.InvariantCulture)))
{
AddAsset(assetList, file, template);
availableShps.Add(file);
@@ -278,8 +197,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic
Func<PaletteFromFile, ScrollItemWidget, ScrollItemWidget> setupItem = (palette, itemTemplate) =>
{
var item = ScrollItemWidget.Setup(itemTemplate,
() => currentPalette.Name == palette.Name,
() => { currentPalette = palette; spriteWidget.Palette = currentPalette.Name; });
() => currentPalette.Name == palette.Name,
() => { currentPalette = palette; spriteWidget.Palette = currentPalette.Name; });
item.Get<LabelWidget>("LABEL").GetText = () => palette.Name;
return item;
};

View File

@@ -1,110 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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.IO;
using System.Linq;
using System.Threading;
using OpenRA.Utility;
using OpenRA.Widgets;
namespace OpenRA.Mods.RA.Widgets.Logic
{
public class ConvertGameFilesLogic
{
Widget panel;
ProgressBarWidget progressBar;
LabelWidget statusLabel;
ButtonWidget retryButton, backButton;
Widget extractingContainer;
string[][] ExtractGameFiles, ExportToPng, ImportFromPng;
[ObjectCreator.UseCtor]
public ConvertGameFilesLogic(Widget widget, string[][] ExtractGameFiles, string[][] ExportToPng, string[][] ImportFromPng)
{
panel = widget.Get("CONVERT_ASSETS_PANEL");
progressBar = panel.Get<ProgressBarWidget>("PROGRESS_BAR");
statusLabel = panel.Get<LabelWidget>("STATUS_LABEL");
backButton = panel.Get<ButtonWidget>("BACK_BUTTON");
backButton.OnClick = Ui.CloseWindow;
retryButton = panel.Get<ButtonWidget>("RETRY_BUTTON");
retryButton.OnClick = Extract;
extractingContainer = panel.Get("EXTRACTING");
this.ExtractGameFiles = ExtractGameFiles;
this.ExportToPng = ExportToPng;
this.ImportFromPng = ImportFromPng;
Extract();
}
void Extract()
{
backButton.IsDisabled = () => true;
retryButton.IsDisabled = () => true;
extractingContainer.IsVisible = () => true;
var onError = (Action<string>)(s => Game.RunAfterTick(() =>
{
statusLabel.GetText = () => "Error: "+s;
backButton.IsDisabled = () => false;
retryButton.IsDisabled = () => false;
}));
var t = new Thread( _ =>
{
try
{
for (int i = 0; i < ExtractGameFiles.Length; i++)
{
progressBar.Percentage = i*100/ExtractGameFiles.Count();
statusLabel.GetText = () => "Extracting...";
Command.ExtractFiles(ExtractGameFiles[i]);
}
for (int i = 0; i < ExportToPng.Length; i++)
{
progressBar.Percentage = i*100/ExportToPng.Count();
statusLabel.GetText = () => "Exporting SHP to PNG...";
Command.ConvertShpToPng(ExportToPng[i]);
}
for (int i = 0; i < ImportFromPng.Length; i++)
{
progressBar.Percentage = i*100/ImportFromPng.Count();
statusLabel.GetText = () => "Converting PNG to SHP...";
Command.ConvertPngToShp(ImportFromPng[i]);
}
Game.RunAfterTick(() =>
{
progressBar.Percentage = 100;
statusLabel.GetText = () => "Done. Check {0}".F(Platform.SupportDir);
backButton.IsDisabled = () => false;
});
}
catch (FileNotFoundException f)
{
onError(f.FileName+" not found.");
}
catch (Exception e)
{
onError(e.Message);
}
}) { IsBackground = true };
t.Start();
}
}
}

View File

@@ -25,6 +25,10 @@ namespace OpenRA.Mods.RA.Widgets
WorldRenderer worldRenderer;
Dictionary<ProductionQueue, Animation> clocks;
public int IconWidth = 32;
public int IconHeight = 24;
public int IconSpacing = 8;
[ObjectCreator.UseCtor]
public ObserverProductionIconsWidget(World world, WorldRenderer worldRenderer)
{
@@ -59,6 +63,8 @@ namespace OpenRA.Mods.RA.Widgets
clocks.Add(queue.Trait, new Animation("clock"));
}
}
var iconSize = new float2(IconWidth, IconHeight);
foreach (var queue in queues)
{
var current = queue.Trait.CurrentItem();
@@ -71,16 +77,15 @@ namespace OpenRA.Mods.RA.Widgets
var icon = new Animation(RenderSimple.GetImage(actor));
icon.Play(actor.Traits.Get<TooltipInfo>().Icon);
var size = icon.Image.size / new float2(2, 2);
var location = new float2(RenderBounds.Location) + new float2(queue.i * (int)size.Length, 0);
WidgetUtils.DrawSHP(icon.Image, location, worldRenderer, size);
var location = new float2(RenderBounds.Location) + new float2(queue.i * (IconWidth + IconSpacing), 0);
WidgetUtils.DrawSHPCentered(icon.Image, location + 0.5f * iconSize, worldRenderer, 0.5f);
var clock = clocks[queue.Trait];
clock.PlayFetchIndex("idle",
() => current.TotalTime == 0 ? 0 : ((current.TotalTime - current.RemainingTime)
* (clock.CurrentSequence.Length - 1) / current.TotalTime));
clock.Tick();
WidgetUtils.DrawSHP(clock.Image, location, worldRenderer, size);
WidgetUtils.DrawSHPCentered(clock.Image, location + 0.5f * iconSize, worldRenderer, 0.5f);
var tiny = Game.Renderer.Fonts["Tiny"];
var text = GetOverlayForItem(current);

View File

@@ -25,6 +25,10 @@ namespace OpenRA.Mods.RA.Widgets
WorldRenderer worldRenderer;
Dictionary<string, Animation> clocks;
public int IconWidth = 32;
public int IconHeight = 24;
public int IconSpacing = 8;
[ObjectCreator.UseCtor]
public ObserverSupportPowerIconsWidget(World world, WorldRenderer worldRenderer)
{
@@ -61,22 +65,24 @@ namespace OpenRA.Mods.RA.Widgets
clocks.Add(power.a.Key, new Animation("clock"));
}
}
var iconSize = new float2(IconWidth, IconHeight);
foreach (var power in powers)
{
var item = power.a.Value;
if (item == null || item.Info == null || item.Info.Icon == null)
continue;
icon.Play(item.Info.Icon);
var size = icon.Image.size / new float2(2, 2);
var location = new float2(RenderBounds.Location) + new float2(power.i * (int)size.Length, 0);
WidgetUtils.DrawSHP(icon.Image, location, worldRenderer, size);
var location = new float2(RenderBounds.Location) + new float2(power.i * (IconWidth + IconSpacing), 0);
WidgetUtils.DrawSHPCentered(icon.Image, location + 0.5f * iconSize, worldRenderer, 0.5f);
var clock = clocks[power.a.Key];
clock.PlayFetchIndex("idle",
() => item.TotalTime == 0 ? 0 : ((item.TotalTime - item.RemainingTime)
* (clock.CurrentSequence.Length - 1) / item.TotalTime));
clock.Tick();
WidgetUtils.DrawSHP(clock.Image, location, worldRenderer, size);
WidgetUtils.DrawSHPCentered(clock.Image, location + 0.5f * iconSize, worldRenderer, 0.5f);
var tiny = Game.Renderer.Fonts["Tiny"];
var text = GetOverlayForItem(item);

View File

@@ -23,6 +23,9 @@ namespace OpenRA.Mods.RA.Widgets
[Translate] public string ReadyText = "";
[Translate] public string HoldText = "";
public int IconWidth = 64;
public int IconHeight = 48;
Animation icon;
Animation clock;
readonly List<Pair<Rectangle, Action<MouseInput>>> buttons = new List<Pair<Rectangle,Action<MouseInput>>>();
@@ -84,10 +87,11 @@ namespace OpenRA.Mods.RA.Widgets
WidgetUtils.DrawRGBA(WidgetUtils.GetChromeImage(world, "specialbin-bottom"), new float2(rectBounds.X, rectBounds.Y + numPowers * 51));
// HACK: Hack Hack Hack
rectBounds.Width = 69;
rectBounds.Height = 10 + numPowers * 51 + 21;
rectBounds.Width = IconWidth + 5;
rectBounds.Height = 31 + numPowers * (IconHeight + 3);
var y = rectBounds.Y + 10;
var iconSize = new float2(IconWidth, IconHeight);
foreach (var kv in powers)
{
var sp = kv.Value;
@@ -137,14 +141,14 @@ namespace OpenRA.Mods.RA.Widgets
}
}
WidgetUtils.DrawSHP(icon.Image, drawPos, worldRenderer);
WidgetUtils.DrawSHPCentered(icon.Image, drawPos + 0.5f * iconSize, worldRenderer);
clock.PlayFetchIndex("idle",
() => sp.TotalTime == 0 ? clock.CurrentSequence.Length - 1 : (sp.TotalTime - sp.RemainingTime)
* (clock.CurrentSequence.Length - 1) / sp.TotalTime);
clock.Tick();
WidgetUtils.DrawSHP(clock.Image, drawPos, worldRenderer);
WidgetUtils.DrawSHPCentered(clock.Image, drawPos + 0.5f * iconSize, worldRenderer);
var overlay = sp.Ready ? ReadyText : sp.Active ? null : HoldText;
var font = Game.Renderer.Fonts["TinyBold"];

View File

@@ -19,6 +19,7 @@ using System.Runtime.InteropServices;
using OpenRA.FileFormats;
using OpenRA.FileFormats.Graphics;
using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Utility
@@ -42,46 +43,37 @@ namespace OpenRA.Utility
public static void ConvertPngToShp(string[] args)
{
var src = args[1];
var dest = Path.ChangeExtension(src, ".shp");
var width = int.Parse(args[2]);
var dest = args[1].Split('-').First() + ".shp";
var frames = args.Skip(1).Select(a => PngLoader.Load(a));
var srcImage = PngLoader.Load(src);
if (srcImage.Width % width != 0)
throw new InvalidOperationException("Bogus width; not a whole number of frames");
var size = frames.First().Size;
if (frames.Any(f => f.Size != size))
throw new InvalidOperationException("All frames must be the same size");
using (var destStream = File.Create(dest))
ShpWriter.Write(destStream, width, srcImage.Height,
srcImage.ToFrames(width));
ShpReader.Write(destStream, size, frames.Select(f => f.ToBytes()));
Console.WriteLine(dest + " saved.");
}
static IEnumerable<byte[]> ToFrames(this Bitmap bitmap, int width)
static byte[] ToBytes(this Bitmap bitmap)
{
for (var x = 0; x < bitmap.Width; x += width)
{
var data = bitmap.LockBits(new Rectangle(x, 0, width, bitmap.Height), ImageLockMode.ReadOnly,
PixelFormat.Format8bppIndexed);
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly,
PixelFormat.Format8bppIndexed);
var bytes = new byte[width * bitmap.Height];
for (var i = 0; i < bitmap.Height; i++)
Marshal.Copy(new IntPtr(data.Scan0.ToInt64() + i * data.Stride),
bytes, i * width, width);
var bytes = new byte[bitmap.Width * bitmap.Height];
for (var i = 0; i < bitmap.Height; i++)
Marshal.Copy(new IntPtr(data.Scan0.ToInt64() + i * data.Stride),
bytes, i * bitmap.Width, bitmap.Width);
bitmap.UnlockBits(data);
bitmap.UnlockBits(data);
yield return bytes;
}
return bytes;
}
public static void ConvertShpToPng(string[] args)
{
var src = args[1];
var dest = Path.ChangeExtension(src, ".png");
var srcImage = ShpReader.Load(src);
var shadowIndex = new int[] { };
if (args.Contains("--noshadow"))
{
@@ -93,134 +85,55 @@ namespace OpenRA.Utility
var palette = Palette.Load(args[2], shadowIndex);
using (var bitmap = new Bitmap(srcImage.ImageCount * srcImage.Width, srcImage.Height, PixelFormat.Format8bppIndexed))
ISpriteSource source;
using (var stream = File.OpenRead(src))
source = SpriteSource.LoadSpriteSource(stream, src);
// The r8 padding requires external information that we can't access here.
var usePadding = !(args.Contains("--nopadding") || source is R8Reader);
var count = 0;
var prefix = Path.GetFileNameWithoutExtension(src);
foreach (var frame in source.Frames)
{
var x = 0;
bitmap.Palette = palette.AsSystemPalette();
var frameSize = usePadding ? frame.FrameSize : frame.Size;
var offset = usePadding ? (frame.Offset - 0.5f * new float2(frame.Size - frame.FrameSize)).ToInt2() : int2.Zero;
foreach (var frame in srcImage.Frames)
// shp(ts) may define empty frames
if (frameSize.Width == 0 && frameSize.Height == 0)
{
var data = bitmap.LockBits(new Rectangle(x, 0, srcImage.Width, srcImage.Height), ImageLockMode.WriteOnly,
PixelFormat.Format8bppIndexed);
count++;
continue;
}
for (var i = 0; i < bitmap.Height; i++)
Marshal.Copy(frame.Image, i * srcImage.Width,
new IntPtr(data.Scan0.ToInt64() + i * data.Stride), srcImage.Width);
using (var bitmap = new Bitmap(frameSize.Width, frameSize.Height, PixelFormat.Format8bppIndexed))
{
bitmap.Palette = palette.AsSystemPalette();
var data = bitmap.LockBits(new Rectangle(0, 0, frameSize.Width, frameSize.Height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
x += srcImage.Width;
// Clear the frame
if (usePadding)
{
var clearRow = new byte[data.Stride];
for (var i = 0; i < frameSize.Height; i++)
Marshal.Copy(clearRow, 0, new IntPtr(data.Scan0.ToInt64() + i * data.Stride), data.Stride);
}
for (var i = 0; i < frame.Size.Height; i++)
{
var destIndex = new IntPtr(data.Scan0.ToInt64() + (i + offset.Y) * data.Stride + offset.X);
Marshal.Copy(frame.Data, i * frame.Size.Width, destIndex, frame.Size.Width);
}
bitmap.UnlockBits(data);
var filename = "{0}-{1:D4}.png".F(prefix, count++);
bitmap.Save(filename);
}
bitmap.Save(dest);
Console.WriteLine(dest + " saved");
}
}
public static void ConvertR8ToPng(string[] args)
{
var srcImage = new R8Reader(File.OpenRead(args[1]));
var shadowIndex = new int[] { };
if (args.Contains("--noshadow"))
{
Array.Resize(ref shadowIndex, shadowIndex.Length + 1);
shadowIndex[shadowIndex.Length - 1] = 3;
}
var palette = Palette.Load(args[2], shadowIndex);
var startFrame = int.Parse(args[3]);
var endFrame = int.Parse(args[4]) + 1;
var filename = args[5];
var frameCount = endFrame - startFrame;
var frame = srcImage[startFrame];
var bitmap = new Bitmap(frame.FrameSize.Width * frameCount, frame.FrameSize.Height, PixelFormat.Format8bppIndexed);
bitmap.Palette = palette.AsSystemPalette();
frame = srcImage[startFrame];
if (args.Contains("--tileset"))
{
int f = 0;
var tileset = new Bitmap(frame.FrameSize.Width * 20, frame.FrameSize.Height * 40, PixelFormat.Format8bppIndexed);
tileset.Palette = palette.AsSystemPalette();
for (int h = 0; h < 40; h++)
{
for (int w = 0; w < 20; w++)
{
if (h * 20 + w < frameCount)
{
Console.WriteLine(f);
frame = srcImage[f];
var data = tileset.LockBits(new Rectangle(w * frame.Size.Width, h * frame.Size.Height, frame.Size.Width, frame.Size.Height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
for (var i = 0; i < frame.Size.Height; i++)
Marshal.Copy(frame.Image, i * frame.Size.Width,
new IntPtr(data.Scan0.ToInt64() + i * data.Stride), frame.Size.Width);
tileset.UnlockBits(data);
f++;
}
}
}
bitmap = tileset;
}
bitmap.Save(filename + ".png");
Console.WriteLine(filename + ".png saved");
}
public static void ConvertTmpToPng(string[] args)
{
var mod = args[1];
var theater = args[2];
var templateNames = args.Skip(3);
var shadowIndex = new int[] { 3, 4 };
var manifest = new Manifest(mod);
FileSystem.LoadFromManifest(manifest);
var tileset = manifest.TileSets.Select(a => new TileSet(a))
.FirstOrDefault(ts => ts.Name == theater);
if (tileset == null)
throw new InvalidOperationException("No theater named '{0}'".F(theater));
var renderer = new TileSetRenderer(tileset, new Size(manifest.TileSize, manifest.TileSize));
var palette = new Palette(FileSystem.Open(tileset.Palette), shadowIndex);
foreach (var templateName in templateNames)
{
var template = tileset.Templates.FirstOrDefault(tt => tt.Value.Image == templateName);
if (template.Value == null)
throw new InvalidOperationException("No such template '{0}'".F(templateName));
using (var image = renderer.RenderTemplate(template.Value.Id, palette))
image.Save(Path.ChangeExtension(templateName, ".png"));
}
}
public static void ConvertFormat2ToFormat80(string[] args)
{
var src = args[1];
var dest = args[2];
Dune2ShpReader srcImage = null;
using (var s = File.OpenRead(src))
srcImage = new Dune2ShpReader(s);
var size = srcImage.First().Size;
if (!srcImage.All(im => im.Size == size))
throw new InvalidOperationException("All the frames must be the same size to convert from Dune2 to RA");
using (var destStream = File.Create(dest))
ShpWriter.Write(destStream, size.Width, size.Height,
srcImage.Select(im => im.Image));
Console.WriteLine("Saved {0}-[0..{1}].png", prefix, count - 1);
}
public static void ExtractFiles(string[] args)
@@ -233,16 +146,12 @@ namespace OpenRA.Utility
foreach (var f in files)
{
if (f == "--userdir")
break;
var src = FileSystem.Open(f);
if (src == null)
throw new InvalidOperationException("File not found: {0}".F(f));
var data = src.ReadAllBytes();
var output = args.Contains("--userdir") ? Platform.SupportDir + f : f;
File.WriteAllBytes(output, data);
Console.WriteLine(output + " saved.");
File.WriteAllBytes(f, data);
Console.WriteLine(f + " saved.");
}
}
@@ -300,8 +209,8 @@ namespace OpenRA.Utility
var srcImage = ShpReader.Load(args[3]);
using (var destStream = File.Create(args[4]))
ShpWriter.Write(destStream, srcImage.Width, srcImage.Height,
srcImage.Frames.Select(im => im.Image.Select(px => (byte)remap[px]).ToArray()));
ShpReader.Write(destStream, srcImage.Size,
srcImage.Frames.Select(im => im.Data.Select(px => (byte)remap[px]).ToArray()));
}
public static void TransposeShp(string[] args)
@@ -323,8 +232,7 @@ namespace OpenRA.Utility
}
using (var destStream = File.Create(args[2]))
ShpWriter.Write(destStream, srcImage.Width, srcImage.Height,
destFrames.Select(f => f.Image));
ShpReader.Write(destStream, srcImage.Size, destFrames.Select(f => f.Data));
}
static string FriendlyTypeName(Type t)

View File

@@ -23,11 +23,8 @@ namespace OpenRA.Utility
{ "--settings-value", Command.Settings },
{ "--shp", Command.ConvertPngToShp },
{ "--png", Command.ConvertShpToPng },
{ "--fromd2", Command.ConvertFormat2ToFormat80 },
{ "--extract", Command.ExtractFiles },
{ "--tmp-png", Command.ConvertTmpToPng },
{ "--remap", Command.RemapShp },
{ "--r8", Command.ConvertR8ToPng },
{ "--transpose", Command.TransposeShp },
{ "--docs", Command.ExtractTraitDocs },
{ "--map-hash", Command.GetMapHash },
@@ -58,13 +55,10 @@ namespace OpenRA.Utility
Console.WriteLine("Usage: OpenRA.Utility.exe [OPTION] [ARGS]");
Console.WriteLine();
Console.WriteLine(" --settings-value KEY Get value of KEY from settings.yaml");
Console.WriteLine(" --shp PNGFILE FRAMEWIDTH Convert a single PNG with multiple frames appended after another to a SHP");
Console.WriteLine(" --png SHPFILE PALETTE [--noshadow] Convert a SHP to a PNG containing all of its frames, optionally removing the shadow");
Console.WriteLine(" --fromd2 DUNE2SHP C&CSHP Convert a Dune II SHP (C&C mouse cursor) to C&C SHP format.");
Console.WriteLine(" --extract MOD[,MOD]* FILES [--userdir] Extract files from mod packages to the current (or user) directory");
Console.WriteLine(" --tmp-png MOD[,MOD]* THEATER FILES Extract terrain tiles to PNG");
Console.WriteLine(" --shp PNGFILE [PNGFILE ...] Combine a list of PNG images into a SHP");
Console.WriteLine(" --png SPRITEFILE PALETTE [--noshadow] [--nopadding] Convert a shp/tmp/R8 to a series of PNGs, optionally removing shadow");
Console.WriteLine(" --extract MOD[,MOD]* FILES Extract files from mod packages to the current directory");
Console.WriteLine(" --remap SRCMOD:PAL DESTMOD:PAL SRCSHP DESTSHP Remap SHPs to another palette");
Console.WriteLine(" --r8 R8FILE PALETTE START END FILENAME [--noshadow] [--tileset] Convert Dune 2000 DATA.R8 to PNGs choosing start- and endframe as well as type to append multiple frames to one PNG named by filename optionally removing the shadow.");
Console.WriteLine(" --transpose SRCSHP DESTSHP START N M [START N M ...] Transpose the N*M block of frames starting at START.");
Console.WriteLine(" --docs MOD Generate trait documentation in MarkDown format.");
Console.WriteLine(" --map-hash MAPFILE Generate hash of specified oramap file.");

View File

@@ -30,8 +30,10 @@ Background@COLOR_CHOOSER:
Width:144
Height:72
ShpImage@FACT:
X:160
Y:13
X:153
Y:1
Width:80
Height:73
Image:fact
Palette:colorpicker
Button@RANDOM_BUTTON:

View File

@@ -4,7 +4,7 @@
Cursors:
mouse2: cursor
mouse2.shp: cursor
scroll-t:
start:1
scroll-tr:
@@ -114,7 +114,7 @@ Cursors:
start:154
length:24
mouse4:cursor
mouse4.shp:cursor
move:
start:0
length:8
@@ -125,7 +125,7 @@ Cursors:
start:0
length: 8
attackmove:cursor
attackmove.shp:cursor
attackmove:
start:0
length:8
@@ -138,7 +138,7 @@ Cursors:
start:13
length:1
mouse3: cursor2
mouse3.shp: cursor2
enter-blocked:
start:212
length:1
@@ -170,7 +170,7 @@ Cursors:
start:213
length:1
nopower: cursor
nopower.shp: cursor
powerdown-blocked:
start:0
length: 1

View File

@@ -1,215 +0,0 @@
Background@ASSETBROWSER_BG:
Logic:AssetBrowserLogic
X:(WINDOW_RIGHT - WIDTH)/2
Y:(WINDOW_BOTTOM - HEIGHT)/2
Width:700
Height:410
Children:
ColorPreviewManager@COLOR_MANAGER:
Label@ASSETBROWSER_TITLE:
X:0
Y:10
Width:PARENT_RIGHT
Height:25
Text:Game Asset Viewer & Converter
Align:Center
Font:Bold
DropDownButton@SOURCE_SELECTOR:
X:40
Y:45
Width:160
Height:25
Font:Bold
Text:Folders
ScrollPanel@ASSET_LIST:
X:40
Y:80
Width:160
Height:190
Children:
ScrollItem@ASSET_TEMPLATE:
Width:PARENT_RIGHT-27
Height:25
X:2
Y:0
Visible:false
Children:
Label@TITLE:
X:10
Width:PARENT_RIGHT-20
Height:25
TextField@FILENAME_INPUT:
X:40
Y:280
Width:140
Height:25
Text:mouse.r8
Button@LOAD_BUTTON:
X:40
Y:310
Width:140
Height:25
Text:Load
Font:Bold
Key:return
DropDownButton@PALETTE_SELECTOR:
X:230
Y:45
Width:150
Height:25
Font:Bold
Text:Palette
DropDownButton@COLOR:
X:380
Y:45
Width:80
Height:25
Children:
ColorBlock@COLORBLOCK:
X:5
Y:6
Width:PARENT_RIGHT-35
Height:PARENT_BOTTOM-12
Background@SPRITE_BG:
X:220
Y:80
Width:250
Height:250
Background:dialog4
Children:
ShpImage@SPRITE:
X:4
Y:4
Width:246
Height:246
Image:mouse.r8
Label@ACTIONS_TITLE:
X:PARENT_RIGHT - 150
Y:45
Width:PARENT_RIGHT
Height:25
Text:Actions
Font:Bold
Button@EXPORT_BUTTON:
X:PARENT_RIGHT - 200
Y:80
Width:160
Height:25
Text:Selected to PNG
Font:Bold
Button@EXTRACT_BUTTON:
X:PARENT_RIGHT - 200
Y:115
Width:160
Height:25
Text:Extract all to PNG
Font:Bold
TextField@IMAGE_FILENAME_INPUT:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 235
Width:100
Height:25
Text:pixelart.png
TextField@IMAGE_SIZE_INPUT:
X:PARENT_RIGHT - 90
Y:PARENT_BOTTOM - 235
Width:50
Height:25
Text:width
Button@IMPORT_BUTTON:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 200
Width:160
Height:25
Text:Import from PNG
Font:Bold
Button@CLOSE_BUTTON:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 115
Width:160
Height:25
Text:Close
Font:Bold
Key:escape
Container@FRAME_SELECTOR:
X:45
Y:360
Children:
Button@BUTTON_PREV:
X:0
Y:0
Width:25
Height:25
Children:
Image@IMAGE_PREV:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:prev
Button@BUTTON_PLAY:
X:35
Y:0
Width:25
Height:25
Children:
Image@IMAGE_PLAY:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:play
Button@BUTTON_PAUSE:
Visible: no
X:35
Y:0
Width:25
Height:25
Children:
Image@IMAGE_PAUSE:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:pause
Button@BUTTON_STOP:
X:70
Y:0
Width:25
Height:25
Children:
Image@IMAGE_STOP:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:stop
Button@BUTTON_NEXT:
X:105
Y:0
Width:25
Height:25
Children:
Image@IMAGE_NEXT:
X:0
Y:0
Width:25
Height:25
ImageCollection:music
ImageName:next
Slider@FRAME_SLIDER:
X:160
Y:0
Width:410
Height:20
MinimumValue: 0
Label@FRAME_COUNT:
X:585
Y:0
Width:25
Height:25
Font:Bold

View File

@@ -30,8 +30,10 @@ Background@COLOR_CHOOSER:
Width:144
Height:72
ShpImage@FACT:
X:160
Y:8
X:153
Y:1
Width:80
Height:73
Image:DATA.R8
Frame:1936
Palette:colorpicker

View File

@@ -150,6 +150,8 @@ Container@PLAYER_WIDGETS:
SupportPowerBin@INGAME_POWERS_BIN:
X:0
Y:25
IconWidth: 60
IconHeight: 48
ReadyText: READY
HoldText: ON HOLD
BuildPalette@INGAME_BUILD_PALETTE:

View File

@@ -2,10 +2,10 @@ ShadowIndex: 1
Palettes:
cursor: cursor.pal
mouse.r8: d2k.pal
mouse: d2k.pal
Cursors:
mouse.r8: mouse.r8
mouse.r8: mouse
scroll-t:
start:112
x: 24
@@ -252,7 +252,7 @@ Cursors:
x: 24
y: 24
nopower: cursor
nopower.shp: cursor
powerdown-blocked:
start:0
length: 1

View File

@@ -76,8 +76,7 @@ ChromeLayout:
mods/ra/chrome/cheats.yaml
mods/ra/chrome/musicplayer.yaml
mods/d2k/chrome/tooltips.yaml
mods/d2k/chrome/assetbrowser.yaml
mods/ra/chrome/convertassets.yaml
mods/ra/chrome/assetbrowser.yaml
mods/ra/chrome/irc.yaml
Weapons:

View File

@@ -1,27 +1,27 @@
carryall:
idle: DATA.R8
idle: DATA
Start: 1923
Facings: -32
unload: DATA.R8
unload: DATA
Start: 1923
Facings: -32
icon: DATA.R8
icon: DATA
Start: 4029
Offset: -30,-24
orni:
idle: DATA.R8
idle: DATA
Start: 1955
Facings: -32
Length: 3
Tick: 120
Transpose: true
icon: DATA.R8
icon: DATA
Start: 4031
Offset: -30,-24
frigate:
idle: DATA.R8
idle: DATA
Start: 2517
Facings: 1

View File

@@ -1,441 +1,441 @@
rifle:
stand: DATA.R8
stand: DATA
Start: 206
Facings: -8
Transpose: true
stand2: DATA.R8
stand2: DATA
Start: 206
Facings: -8
Transpose: true
stand3: DATA.R8
stand3: DATA
Start: 206
Facings: -8
Transpose: true
run: DATA.R8
run: DATA
Start: 214
Length: 6
Facings: -8
Tick: 110
Transpose: true
shoot: DATA.R8
shoot: DATA
Start: 254
Length: 6
Facings: -8
Transpose: true
prone-stand: DATA.R8
prone-stand: DATA
Start: 302
Facings: -8
Transpose: true
prone-run: DATA.R8
prone-run: DATA
Start: 310
Length: 3
Facings: -8
Transpose: true
Tick: 110
standup-0: DATA.R8
standup-0: DATA
Start: 302
Facings: -8
Transpose: true
Tick: 120
prone-shoot: DATA.R8
prone-shoot: DATA
Start: 334
Length: 6
Facings: -8
Transpose: true
die1: DATA.R8
die1: DATA
Start: 382
Length: 5
die2: DATA.R8
die2: DATA
Start: 387
Length: 7
Tick: 80
die3: DATA.R8
die3: DATA
Start: 394
Length: 7
die4: DATA.R8
die4: DATA
Start: 401
Length: 7
die5: DATA.R8
die5: DATA
Start: 408
Length: 7
die6: DATA.R8
die6: DATA
Start: 415
Length: 12
die-crushed: DATA.R8
die-crushed: DATA
Start: 430
Length: 12
Tick: 1600
icon: DATA.R8
icon: DATA
Start: 4011
Offset: -30,-24
bazooka:
stand: DATA.R8
stand: DATA
Start: 458
Facings: -8
Transpose: true
stand2: DATA.R8
stand2: DATA
Start: 458
Facings: -8
Transpose: true
stand3: DATA.R8
stand3: DATA
Start: 458
Facings: -8
Transpose: true
run: DATA.R8
run: DATA
Start: 466
Length: 6
Facings: -8
Tick: 120
Transpose: true
shoot: DATA.R8
shoot: DATA
Start: 506
Length: 6
Facings: -8
Transpose: true
prone-stand: DATA.R8
prone-stand: DATA
Start: 562
Length: 1
Facings: -8
Transpose: true
prone-run: DATA.R8
prone-run: DATA
Start: 570
Length: 3
Facings: -8
Transpose: true
Tick: 120
standup-0: DATA.R8
standup-0: DATA
Start: 554
Length: 1
Facings: -8
Transpose: true
prone-shoot: DATA.R8
prone-shoot: DATA
Start: 586
Length: 6
Facings: -8
Transpose: true
die1: DATA.R8
die1: DATA
Start: 634
Length: 5
die2: DATA.R8
die2: DATA
Start: 639
Length: 7
die3: DATA.R8
die3: DATA
Start: 646
Length: 7
die4: DATA.R8
die4: DATA
Start: 653
Length: 7
die5: DATA.R8
die5: DATA
Start: 660
Length: 7
die6: DATA.R8
die6: DATA
Start: 660
Length: 7
die-crushed: DATA.R8
die-crushed: DATA
Start: 668
Length: 26
Tick: 1600
icon: DATA.R8
icon: DATA
Start: 4012
Offset: -30,-24
engineer:
stand: DATA.R8
stand: DATA
Start: 1166
Facings: -8
Transpose: true
stand2: DATA.R8
stand2: DATA
Start: 1166
Facings: -8
Transpose: true
run: DATA.R8
run: DATA
Start: 1174
Length: 6
Facings: -8
Transpose: true
Tick: 120
die1: DATA.R8
die1: DATA
Start: 1342
Length: 5
die2: DATA.R8
die2: DATA
Start: 1347
Length: 7
die3: DATA.R8
die3: DATA
Start: 1354
Length: 7
die4: DATA.R8
die4: DATA
Start: 1361
Length: 7
die5: DATA.R8
die5: DATA
Start: 1368
Length: 7
die6: DATA.R8
die6: DATA
Start: 1368
Length: 7
die-crushed: DATA.R8
die-crushed: DATA
Start: 1376
Length: 26
Tick: 1600
icon: DATA.R8
icon: DATA
Start: 4013
Offset: -30,-24
medic: # actually thumper
stand: DATA.R8
stand: DATA
Start: 1402
Facings: -8
Transpose: true
stand2: DATA.R8
stand2: DATA
Start: 1402
Facings: -8
Transpose: true
run: DATA.R8
run: DATA
Start: 1410
Length: 6
Facings: -8
Transpose: true
Tick: 120
heal: DATA.R8
heal: DATA
Start: 1458
Length: 5
Tick: 480
die1: DATA.R8
die1: DATA
Start: 1543
Length: 5
die2: DATA.R8
die2: DATA
Start: 1548
Length: 7
die3: DATA.R8
die3: DATA
Start: 1555
Length: 7
die4: DATA.R8
die4: DATA
Start: 1562
Length: 7
die5: DATA.R8
die5: DATA
Start: 1569
Length: 7
die6: DATA.R8
die6: DATA
Start: 1569
Length: 7
die-crushed: DATA.R8
die-crushed: DATA
Start: 1577
Length: 26
Tick: 1600
icon: DATA.R8
icon: DATA
Start: 4014
Offset: -30,-24
thumping:
idle: DATA.R8
idle: DATA
Start: 1458
Length: 5
Tick: 150
make: DATA.R8
make: DATA
Start: 1458
Length: 5
damaged-idle: DATA.R8
damaged-idle: DATA
Start: 1458
Length: 5
Tick: 150
icon: DATA.R8
icon: DATA
Frames: 4014
Offset: -30,-24
fremen:
stand: DATA.R8
stand: DATA
Start: 694
Facings: -8
Transpose: true
stand2: DATA.R8
stand2: DATA
Start: 694
Facings: -8
Transpose: true
run: DATA.R8
run: DATA
Start: 702
Length: 6
Facings: -8
Transpose: true
Tick: 120
shoot: DATA.R8
shoot: DATA
Start: 742
Length: 6
Facings: -8
Transpose: true
prone-stand: DATA.R8
prone-stand: DATA
Start: 798
Length: 1
Facings: -8
Transpose: true
prone-run: DATA.R8
prone-run: DATA
Start: 806
Length: 3
Facings: -8
Transpose: true
Tick: 120
standup-0: DATA.R8
standup-0: DATA
Start: 790
Length: 1
Facings: -8
Transpose: true
Tick: 120
prone-shoot: DATA.R8
prone-shoot: DATA
Start: 822
Length: 6
Facings: -8
Transpose: true
die1: DATA.R8
die1: DATA
Start: 870
Length: 5
die2: DATA.R8
die2: DATA
Start: 875
Length: 7
die3: DATA.R8
die3: DATA
Start: 882
Length: 7
die4: DATA.R8
die4: DATA
Start: 889
Length: 7
die5: DATA.R8
die5: DATA
Start: 896
Length: 7
die6: DATA.R8
die6: DATA
Start: 896
Length: 7
die-crushed: DATA.R8
die-crushed: DATA
Start: 904
Length: 26
Tick: 1600
icon: DATA.R8
icon: DATA
Start: 4032
Offset: -30,-24
saboteur:
stand: DATA.R8
stand: DATA
Start: 2149
Facings: -8
Transpose: true
stand2: DATA.R8
stand2: DATA
Start: 2149
Facings: -8
Transpose: true
stand3: DATA.R8
stand3: DATA
Start: 2149
Facings: -8
Transpose: true
run: DATA.R8
run: DATA
Start: 2157
Length: 6
Facings: -8
Transpose: true
Tick: 120
prone-stand: DATA.R8
prone-stand: DATA
Start: 2253
Length: 1
Facings: -8
Transpose: true
prone-run: DATA.R8
prone-run: DATA
Start: 2261
Length: 3
Facings: -8
Transpose: true
Tick: 120
standup-0: DATA.R8
standup-0: DATA
Start: 2245
Length: 1
Facings: -8
Transpose: true
Tick: 120
die1: DATA.R8
die1: DATA
Start: 2325
Length: 5
die2: DATA.R8
die2: DATA
Start: 2330
Length: 7
die3: DATA.R8
die3: DATA
Start: 2337
Length: 7
die4: DATA.R8
die4: DATA
Start: 2344
Length: 7
die5: DATA.R8
die5: DATA
Start: 2351
Length: 7
die6: DATA.R8
die6: DATA
Start: 2351
Length: 7
die-crushed: DATA.R8
die-crushed: DATA
Start: 2359
Length: 26
Tick: 1600
icon: DATA.R8
icon: DATA
Start: 4034
Offset: -30,-24
sardaukar:
stand: DATA.R8
stand: DATA
Start: 930
Facings: -8
Transpose: true
stand2: DATA.R8
stand2: DATA
Start: 930
Facings: -8
Transpose: true
run: DATA.R8
run: DATA
Start: 938
Length: 6
Facings: -8
Transpose: true
Tick: 120
shoot: DATA.R8
shoot: DATA
Start: 978
Length: 6
Facings: -8
Transpose: true
prone-stand: DATA.R8
prone-stand: DATA
Start: 1034
Length: 1
Facings: -8
Transpose: true
prone-run: DATA.R8
prone-run: DATA
Start: 1042
Length: 3
Facings: -8
Transpose: true
Tick: 120
standup-0: DATA.R8
standup-0: DATA
Start: 1026
Length: 1
Facings: -8
Transpose: true
Tick: 120
prone-shoot: DATA.R8
prone-shoot: DATA
Start: 1058
Length: 6
Facings: 8
Transpose: true
die1: DATA.R8
die1: DATA
Start: 1106
Length: 5
die2: DATA.R8
die2: DATA
Start: 1111
Length: 7
die3: DATA.R8
die3: DATA
Start: 1118
Length: 7
die4: DATA.R8
die4: DATA
Start: 1125
Length: 7
die5: DATA.R8
die5: DATA
Start: 1132
Length: 7
die6: DATA.R8
die6: DATA
Start: 1132
Length: 7
die-crushed: DATA.R8
die-crushed: DATA
Start: 1140
Length: 26
Tick: 1600
icon: DATA.R8
icon: DATA
Start: 4015
Offset: -30,-24

View File

@@ -1,87 +1,87 @@
explosion:
piff: DATA.R8
piff: DATA
Start: 3626
Length: 5
piffs: DATA.R8
piffs: DATA
Start: 3429
Length: 4
Tick: 80
BlendMode: Additive
small_explosion: DATA.R8
small_explosion: DATA
Start: 3403
Length: 15
BlendMode: Additive
med_explosion: DATA.R8
med_explosion: DATA
Start: 3390
Length: 12
BlendMode: Additive
tiny_explosion: DATA.R8
tiny_explosion: DATA
Start: 3386
Length: 4
Tick: 80
BlendMode: Additive
nuke: DATA.R8
nuke: DATA
Start: 3965
Length: 14
Tick: 60
BlendMode: Additive
mini_explosion: DATA.R8
mini_explosion: DATA
Start: 3403
Length: 15
Tick: 60
BlendMode: Additive
self_destruct: DATA.R8
self_destruct: DATA
Start: 3433
Length: 15
BlendMode: Additive
building: DATA.R8
building: DATA
Start: 3448
Length: 22
BlendMode: Additive
large_explosion: DATA.R8
large_explosion: DATA
Start: 3988
Length: 22
BlendMode: Additive
artillery: DATA.R8
artillery: DATA
Start: 3988
Length: 22
BlendMode: Additive
small_artillery: DATA.R8
small_artillery: DATA
Start: 3390
Length: 12
Tick: 60
BlendMode: Additive
small_napalm: DATA.R8
small_napalm: DATA
Start: 3421
Length: 8
BlendMode: Additive
shockwave: DATA.R8
shockwave: DATA
Start: 3687
Length: 6
BlendMode: Additive
Tick: 120
deviator: DATA.R8
deviator: DATA
Start: 3512
Length: 23
BlendMode: Additive
Tick: 60
laserfire:
idle: DATA.R8
idle: DATA
Start: 3386
Length: 4
Tick: 80
BlendMode: Additive
pips:
groups: DATA.R8
groups: DATA
Start: 17
Length: 10
tag-primary: DATA.R8
tag-primary: DATA
Start: 110
pip-empty: DATA.R8
pip-empty: DATA
Start: 15
pip-green: DATA.R8
pip-green: DATA
Start: 16
clock:
@@ -105,19 +105,19 @@ rank:
Length: *
overlay:
build-valid-arrakis: DATA.R8
build-valid-arrakis: DATA
Start: 0
Offset: -16,-16
build-invalid: DATA.R8
build-invalid: DATA
Start: 1
Offset: -16,-16
target-select: DATA.R8
target-select: DATA
Start: 2
Offset: -16,-16
target-valid-arrakis: DATA.R8
target-valid-arrakis: DATA
Start: 0
Offset: -16,-16
target-invalid: DATA.R8
target-invalid: DATA
Start: 1
Offset: -16,-16
@@ -131,29 +131,29 @@ rallypoint:
Length: *
rpg:
idle: DATA.R8
idle: DATA
Start: 3015
Facings: -32
120mm:
idle: DATA.R8
idle: DATA
Start: 3014
Length: 1
BlendMode: Additive
155mm:
idle: DATA.R8
idle: DATA
Start: 3081
Length: 1
crate-effects:
dollar: DATA.R8
dollar: DATA
Start: 3679
Length: 8
reveal-map: DATA.R8
reveal-map: DATA
Start: 3947
Length: 18
hide-map: DATA.R8
hide-map: DATA
Start: 3911
Length: 36
levelup: levelup
@@ -162,70 +162,70 @@ crate-effects:
Tick: 200
allyrepair:
repair: DATA.R8
repair: DATA
Frames: 3, 39
Length: 2
Tick: 300
missile:
idle: DATA.R8
idle: DATA
Start: 3088
Facings: -32
missile2:
idle: DATA.R8
idle: DATA
Start: 3306
Facings: -32
atomic:
up: DATA.R8
up: DATA
Start: 2147
Length: 1
down: DATA.R8
down: DATA
Start: 2148
Length: 1
fire:
1: DATA.R8
1: DATA
Start: 3712
Length: 10
Offset: 0,-3
BlendMode: Additive
2: DATA.R8
2: DATA
Start: 3723
Length: 11
Offset: 0,-3
BlendMode: Additive
3: DATA.R8
3: DATA
Start: 3885
Length: 13
Offset: 0,-3
BlendMode: Additive
4: DATA.R8
4: DATA
Start: 3712
Length: 10
Offset: 0,-3
BlendMode: Additive
smoke_m:
idle: DATA.R8
idle: DATA
Start: 3418
Length: 2
BlendMode: Additive
loop: DATA.R8
loop: DATA
Start: 3418
Length: 2
BlendMode: Additive
end: DATA.R8
end: DATA
Start: 3418
Length: 3
BlendMode: Additive
bombs:
open: DATA.R8
open: DATA
Start: 3280
Length: 4
idle: DATA.R8
idle: DATA
Start: 3280
Length: 4
@@ -248,59 +248,59 @@ waypoint:
Length: *
sietch:
idle: DATA.R8
idle: DATA
Start: 2998
doubleblast:
idle: DATA.R8
idle: DATA
Start: 3279
Facings: -16
BlendMode: Additive
doubleblastbullet:
idle: DATA.R8
idle: DATA
Start: 3248
Facings: -16
BlendMode: Additive
icon:
paratroopers: DATA.R8
paratroopers: DATA
Start: 4029
Offset: -30,-24
ornistrike: DATA.R8
ornistrike: DATA
Start: 4031
Offset: -30,-24
deathhand: DATA.R8
deathhand: DATA
Start: 4035
Offset: -30,-24
crate:
idle: DATA.R8
idle: DATA
Start: 102
ZOffset: -511
Offset: -16,-16
land: DATA.R8
land: DATA
Start: 102
ZOffset: -511
Offset: -16,-16
spicebloom:
make: DATA.R8
make: DATA
Start: 107
Length: 3
Offset: -16,-16
active: DATA.R8
active: DATA
Start: 109
Length: 1
ZOffset: -511
Offset: -16,-16
idle: DATA.R8
idle: DATA
Start: 109
ZOffset: -511
Offset: -16,-16
moveflsh:
idle: DATA.R8
idle: DATA
Start: 3621
Length: 5
Tick: 80

File diff suppressed because it is too large Load Diff

View File

@@ -1,174 +1,174 @@
dmcv:
idle: DATA.R8
idle: DATA
Start: 1795
Facings: -32
icon: DATA.R8
icon: DATA
Start: 4023
Offset: -30,-24
harvester:
idle: DATA.R8
idle: DATA
Start: 1699
Facings: -32
harvest: DATA.R8
harvest: DATA
Start: 3631
Length: 6
Facings: -8
Tick: 80
ZOffset: 1
dock: DATA.R8
dock: DATA
Start: 3370
Length: 10
dock-loop: DATA.R8
dock-loop: DATA
Start: 3380
Length: 1
icon: DATA.R8
icon: DATA
Start: 4019
Offset: -30,-24
trike:
idle: DATA.R8
idle: DATA
Start: 1635
Facings: -32
unload: DATA.R8
unload: DATA
Start: 1635
Facings: -32
muzzle: DATA.R8
muzzle: DATA
Frames: 3839, 3839, 3840, 3840, 3841, 3841, 3842, 3842, 3843, 3843, 3844, 3844, 3845, 3845, 3846, 3846, 3847, 3847, 3848, 3848, 3849, 3849, 3850, 3850, 3851, 3851, 3852, 3852, 3853, 3853, 3854, 3854, 3855, 3855, 3856, 3856, 3857, 3857, 3858, 3858, 3859, 3859, 3860, 3860, 3861, 3861, 3862, 3862, 3863, 3863, 3864, 3864, 3865, 3865, 3866, 3866, 3867, 3867, 3868, 3868, 3869, 3869, 3870, 3870
Facings: -32
Length: 2
BlendMode: Additive
icon: DATA.R8
icon: DATA
Start: 4041
Offset: -30,-24
quad:
idle: DATA.R8
idle: DATA
Start: 1667
Facings: -32
unload: DATA.R8
unload: DATA
Start: 1667
Facings: -32
icon: DATA.R8
icon: DATA
Start: 4018
Offset: -30,-24
siegetank:
idle: DATA.R8
idle: DATA
Start: 1763
Facings: -32
turret: DATA.R8
turret: DATA
Start: 1891
Facings: -32
muzzle: DATA.R8
muzzle: DATA
Start: 3418
Length: 3
BlendMode: Additive
icon: DATA.R8
icon: DATA
Start: 4026
Offset: -30,-24
missiletank:
idle: DATA.R8
idle: DATA
Start: 1603
Facings: -32
icon: DATA.R8
icon: DATA
Start: 4024
Offset: -30,-24
sonictank:
idle: DATA.R8
idle: DATA
Start: 1827
Facings: -32
icon: DATA.R8
icon: DATA
Start: 4027
Offset: -30,-24
combata:
idle: DATA.R8
idle: DATA
Start: 1731
Facings: -32
turret: DATA.R8
turret: DATA
Start: 1859
Facings: -32
muzzle: DATA.R8
muzzle: DATA
Frames: 3775, 3775, 3776, 3776, 3777, 3777, 3778, 3778, 3779, 3779, 3780, 3780, 3781, 3781, 3782, 3782, 3783, 3783, 3784, 3784, 3785, 3785, 3786, 3786, 3787, 3787, 3788, 3788, 3789, 3789, 3790, 3790, 3791, 3791, 3792, 3792, 3793, 3793, 3794, 3794, 3795, 3795, 3796, 3796, 3797, 3797, 3798, 3798, 3799, 3799, 3800, 3800, 3801, 3801, 3802, 3802, 3803, 3803, 3804, 3804, 3805, 3805, 3806, 3806
Facings: -32
Length: 2
BlendMode: Additive
icon: DATA.R8
icon: DATA
Start: 4020
Offset: -30,-24
combath:
idle: DATA.R8
idle: DATA
Start: 2051
Facings: -32
turret: DATA.R8
turret: DATA
Start: 2115
Facings: -32
muzzle: DATA.R8
muzzle: DATA
Frames: 3775, 3775, 3776, 3776, 3777, 3777, 3778, 3778, 3779, 3779, 3780, 3780, 3781, 3781, 3782, 3782, 3783, 3783, 3784, 3784, 3785, 3785, 3786, 3786, 3787, 3787, 3788, 3788, 3789, 3789, 3790, 3790, 3791, 3791, 3792, 3792, 3793, 3793, 3794, 3794, 3795, 3795, 3796, 3796, 3797, 3797, 3798, 3798, 3799, 3799, 3800, 3800, 3801, 3801, 3802, 3802, 3803, 3803, 3804, 3804, 3805, 3805, 3806, 3806
Facings: -32
Length: 2
BlendMode: Additive
icon: DATA.R8
icon: DATA
Start: 4021
Offset: -30,-24
devast:
idle: DATA.R8
idle: DATA
Start: 2083
Facings: -32
muzzle: DATA.R8
muzzle: DATA
Frames: 3807, 3807, 3808, 3808, 3809, 3809, 3810, 3810, 3810, 3811, 3811, 3812, 3812, 3813, 3813, 3814, 3814, 3815, 3816, 3816, 3817, 3817, 3818, 3819, 3819, 3820, 3820, 3821, 3821, 3822, 3822, 3823, 3823, 3824, 3824, 3825, 3825, 3826, 3826, 3827, 3827, 3828, 3828, 3829, 3829, 3830, 3830, 3831, 3831, 3832, 3832, 3832, 3833, 3833, 3834, 3834, 3835, 3835, 3836, 3836, 3837, 3837, 3838, 3838
Facings: -32
Length: 2
BlendMode: Additive
icon: DATA.R8
icon: DATA
Start: 4028
Offset: -30,-24
combato:
idle: DATA.R8
idle: DATA
Start: 2453
Facings: -32
turret: DATA.R8
turret: DATA
Start: 2485
Facings: -32
muzzle: DATA.R8
muzzle: DATA
Frames: 3775, 3775, 3776, 3776, 3777, 3777, 3778, 3778, 3779, 3779, 3780, 3780, 3781, 3781, 3782, 3782, 3783, 3783, 3784, 3784, 3785, 3785, 3786, 3786, 3787, 3787, 3788, 3788, 3789, 3789, 3790, 3790, 3791, 3791, 3792, 3792, 3793, 3793, 3794, 3794, 3795, 3795, 3796, 3796, 3797, 3797, 3798, 3798, 3799, 3799, 3800, 3800, 3801, 3801, 3802, 3802, 3803, 3803, 3804, 3804, 3805, 3805, 3806, 3806
Facings: -32
Length: 2
BlendMode: Additive
icon: DATA.R8
icon: DATA
Start: 4022
Offset: -30,-24
raider:
idle: DATA.R8
idle: DATA
Start: 2421
Facings: -32
unload: DATA.R8
unload: DATA
Start: 2421
Facings: -32
muzzle: DATA.R8
muzzle: DATA
Frames: 3743, 3743, 3744, 3744, 3745, 3745, 3746, 3746, 3747, 3747, 3748, 3748, 3749, 3749, 3750, 3750, 3751, 3751, 3752, 3752, 3753, 3753, 3754, 3754, 3755, 3755, 3756, 3756, 3757, 3757, 3758, 3758, 3759, 3759, 3760, 3760, 3761, 3761, 3762, 3762, 3763, 3763, 3764, 3764, 3765, 3765, 3766, 3766, 3767, 3767, 3768, 3768, 3769, 3769, 3770, 3770, 3771, 3771, 3772, 3772, 3773, 3773, 3774, 3774
Facings: -32
Length: 2
BlendMode: Additive
icon: DATA.R8
icon: DATA
Start: 4017
Offset: -30,-24
stealthraider:
idle: DATA.R8
idle: DATA
Start: 2421
Facings: -32
unload: DATA.R8
unload: DATA
Start: 2421
Facings: -32
muzzle: DATA.R8
muzzle: DATA
Frames: 3743, 3743, 3744, 3744, 3745, 3745, 3746, 3746, 3747, 3747, 3748, 3748, 3749, 3749, 3750, 3750, 3751, 3751, 3752, 3752, 3753, 3753, 3754, 3754, 3755, 3755, 3756, 3756, 3757, 3757, 3758, 3758, 3759, 3759, 3760, 3760, 3761, 3761, 3762, 3762, 3763, 3763, 3764, 3764, 3765, 3765, 3766, 3766, 3767, 3767, 3768, 3768, 3769, 3769, 3770, 3770, 3771, 3771, 3772, 3772, 3773, 3773, 3774, 3774
Facings: -32
Length: 2
@@ -177,9 +177,9 @@ stealthraider:
Start: 0
deviatortank:
idle: DATA.R8
idle: DATA
Start: 2389
Facings: -32
icon: DATA.R8
icon: DATA
Start: 4025
Offset: -30,-24

View File

@@ -3,7 +3,7 @@ General:
Id: ARRAKIS
SheetSize: 1024
Palette: d2k.pal
Extensions: .R8, .shp
Extensions: .R8, .r8, .shp
Terrain:
TerrainType@Sand:

View File

@@ -3,29 +3,36 @@ Background@ASSETBROWSER_BG:
X:(WINDOW_RIGHT - WIDTH)/2
Y:(WINDOW_BOTTOM - HEIGHT)/2
Width:700
Height:410
Height:500
Children:
ColorPreviewManager@COLOR_MANAGER:
Label@ASSETBROWSER_TITLE:
X:0
Y:10
Y:20
Width:PARENT_RIGHT
Height:25
Text:Game Asset Viewer & Converter
Align:Center
Font:Bold
Align:Center
Text:Asset Browser
Label@SOURCE_SELECTOR_DESC:
X:20
Y:35
Width:160
Height:25
Font:TinyBold
Align:Center
Text:Select asset source
DropDownButton@SOURCE_SELECTOR:
X:40
Y:45
X:20
Y:60
Width:160
Height:25
Font:Bold
Text:Folders
ScrollPanel@ASSET_LIST:
X:40
Y:80
X:20
Y:90
Width:160
Height:190
Height:275
Children:
ScrollItem@ASSET_TEMPLATE:
Width:PARENT_RIGHT-27
@@ -38,30 +45,45 @@ Background@ASSETBROWSER_BG:
X:10
Width:PARENT_RIGHT-20
Height:25
Label@FILENAME_DESC:
X:20
Y:370
Width:160
Height:25
Font:TinyBold
Align:Center
Text:Search for file
TextField@FILENAME_INPUT:
X:40
Y:280
Width:140
X:20
Y:395
Width:160
Height:25
Text:mouse.shp
Button@LOAD_BUTTON:
X:40
Y:310
Width:140
X:20
Y:425
Width:160
Height:25
Text:Load
Font:Bold
Key:return
DropDownButton@PALETTE_SELECTOR:
X:230
Y:45
Label@PALETTE_DESC:
X:PARENT_RIGHT-WIDTH-270
Y:60
Width:150
Height:25
Font:Bold
Align:Right
Text:Palette:
DropDownButton@PALETTE_SELECTOR:
X:PARENT_RIGHT-WIDTH-110
Y:60
Width:150
Height:25
Font:Bold
Text:Palette
DropDownButton@COLOR:
X:380
Y:45
X:PARENT_RIGHT-WIDTH-20
Y:60
Width:80
Height:25
Children:
@@ -71,69 +93,19 @@ Background@ASSETBROWSER_BG:
Width:PARENT_RIGHT-35
Height:PARENT_BOTTOM-12
Background@SPRITE_BG:
X:220
Y:80
Width:250
Height:250
Background:dialog4
X:190
Y:90
Width:490
Height:330
Background:dialog3
Children:
ShpImage@SPRITE:
X:4
Y:4
Width:246
Height:246
Width:PARENT_RIGHT
Height:PARENT_BOTTOM
Image:mouse
Label@ACTIONS_TITLE:
X:PARENT_RIGHT - 150
Y:45
Width:PARENT_RIGHT
Height:25
Text:Actions
Font:Bold
Button@EXPORT_BUTTON:
X:PARENT_RIGHT - 200
Y:80
Width:160
Height:25
Text:Selected to PNG
Font:Bold
Button@EXTRACT_BUTTON:
X:PARENT_RIGHT - 200
Y:115
Width:160
Height:25
Text:Extract all to PNG
Font:Bold
TextField@IMAGE_FILENAME_INPUT:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 235
Width:100
Height:25
Text:pixelart.png
TextField@IMAGE_SIZE_INPUT:
X:PARENT_RIGHT - 90
Y:PARENT_BOTTOM - 235
Width:50
Height:25
Text:width
Button@IMPORT_BUTTON:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 200
Width:160
Height:25
Text:Import from PNG
Font:Bold
Button@CLOSE_BUTTON:
X:PARENT_RIGHT - 200
Y:PARENT_BOTTOM - 115
Width:160
Height:25
Text:Close
Font:Bold
Key:escape
Container@FRAME_SELECTOR:
X:45
Y:360
X:190
Y:425
Children:
Button@BUTTON_PREV:
X:0
@@ -202,14 +174,22 @@ Background@ASSETBROWSER_BG:
ImageCollection:music
ImageName:next
Slider@FRAME_SLIDER:
X:160
Y:0
Width:410
X:140
Y:3
Width:300
Height:20
MinimumValue: 0
Label@FRAME_COUNT:
X:585
Y:0
Width:25
X:445
Width:40
Height:25
Font:Bold
Font:TinyBold
Align:Left
Button@CLOSE_BUTTON:
Key:escape
X:PARENT_RIGHT-180
Y:PARENT_BOTTOM-45
Width:160
Height:25
Font:Bold
Text:Close

View File

@@ -30,8 +30,10 @@ Background@COLOR_CHOOSER:
Width:144
Height:72
ShpImage@FACT:
X:156
X:153
Y:1
Width:80
Height:73
Image:fact
Palette:colorpicker
Button@RANDOM_BUTTON:

View File

@@ -1,47 +0,0 @@
Background@CONVERT_ASSETS_PANEL:
Logic:ConvertGameFilesLogic
X:(WINDOW_RIGHT - WIDTH)/2
Y:(WINDOW_BOTTOM - HEIGHT)/2
Width:500
Height:160
Children:
Label@TITLE:
X:0
Y:20
Width:PARENT_RIGHT
Height:25
Text:Extracting and Converting Gamefiles
Align:Center
Font:Bold
Container@EXTRACTING:
Width:PARENT_RIGHT
Height:PARENT_BOTTOM
Visible: false
Children:
ProgressBar@PROGRESS_BAR:
X:50
Y:55
Width:PARENT_RIGHT - 100
Height:25
Label@STATUS_LABEL:
X:50
Y:80
Width:PARENT_RIGHT - 100
Height:25
Align:Left
Button@RETRY_BUTTON:
X:PARENT_RIGHT - 280
Y:PARENT_BOTTOM - 45
Width:120
Height:25
Text:Retry
Font:Bold
Key:return
Button@BACK_BUTTON:
X:PARENT_RIGHT - 140
Y:PARENT_BOTTOM - 45
Width:120
Height:25
Text:Back
Font:Bold
Key:escape

View File

@@ -7,14 +7,15 @@ Background@MODS_PANEL:
Children:
Label@TITLE:
Text:Select Mod
Width:PARENT_RIGHT
Y:20
Width:PARENT_RIGHT
Height:25
Font:Bold
Align:Center
ScrollPanel@MOD_LIST:
X:15
X:20
Y:70
Width:710
Width:700
Height:PARENT_BOTTOM - 125
Children:
ScrollItem@MOD_TEMPLATE:

View File

@@ -2,7 +2,7 @@
cursor: cursor.pal
Cursors:
mouse: cursor
mouse.shp: cursor
scroll-t:
start:1
scroll-tr:
@@ -163,7 +163,7 @@ Cursors:
start:148
length: 12
nopower: cursor
nopower.shp: cursor
powerdown-blocked:
start:0
length: 1

View File

@@ -92,7 +92,6 @@ ChromeLayout:
mods/ra/chrome/musicplayer.yaml
mods/ra/chrome/tooltips.yaml
mods/ra/chrome/assetbrowser.yaml
mods/ra/chrome/convertassets.yaml
mods/ra/chrome/irc.yaml
Weapons:

View File

@@ -30,8 +30,10 @@ Background@COLOR_CHOOSER:
Width:144
Height:72
ShpImage@GTCNST:
X:121
Y:0-68
X:153
Y:1-40
Width:80
Height:73
Image:gtcnstmk
Palette:colorpicker
Button@RANDOM_BUTTON:

View File

@@ -2,7 +2,7 @@
cursor: mousepal.pal
Cursors:
mouse: cursor
mouse.shp: cursor
scroll-t: #TODO
start: 2
scroll-tr: #TODO

View File

@@ -120,7 +120,6 @@ ChromeLayout:
mods/ra/chrome/musicplayer.yaml
mods/ra/chrome/tooltips.yaml
mods/ra/chrome/assetbrowser.yaml
mods/ra/chrome/convertassets.yaml
mods/ra/chrome/irc.yaml
Weapons: