Support for Dune II SHP files used for cursors in Red Alert.
This commit is contained in:
129
OpenRa.FileFormats/Dune2ShpReader.cs
Normal file
129
OpenRa.FileFormats/Dune2ShpReader.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
|
||||
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(BinaryReader reader)
|
||||
{
|
||||
Flags = (Dune2ImageFlags)reader.ReadUInt16();
|
||||
Slices = reader.ReadByte();
|
||||
Width = reader.ReadUInt16();
|
||||
Height = reader.ReadByte();
|
||||
FileSize = reader.ReadUInt16();
|
||||
DataSize = reader.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 ? reader.ReadByte() : 16;
|
||||
LookupTable = new byte[n];
|
||||
for (int i = 0; i < n; i++)
|
||||
LookupTable[i] = reader.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
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 stream)
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
|
||||
ImageCount = reader.ReadUInt16();
|
||||
|
||||
//Last offset is pointer to end of file.
|
||||
uint[] offsets = new uint[ImageCount + 1];
|
||||
|
||||
uint temp = reader.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 ? reader.ReadUInt16() : reader.ReadUInt32()) + 2;
|
||||
|
||||
for (int i = 0; i < ImageCount; i++)
|
||||
{
|
||||
reader.BaseStream.Seek(offsets[i], SeekOrigin.Begin);
|
||||
Dune2ImageHeader header = new Dune2ImageHeader(reader);
|
||||
byte[] imgData = reader.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
31
OpenRa.FileFormats/Format2.cs
Normal file
31
OpenRa.FileFormats/Format2.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRa.FileFormats
|
||||
{
|
||||
public static class Format2
|
||||
{
|
||||
public static int DecodeInto(byte[] src, byte[] dest)
|
||||
{
|
||||
FastByteReader r = new FastByteReader(src);
|
||||
|
||||
int i = 0;
|
||||
while (!r.Done())
|
||||
{
|
||||
byte cmd = r.ReadByte();
|
||||
if (cmd == 0)
|
||||
{
|
||||
byte count = r.ReadByte();
|
||||
while (count-- > 0)
|
||||
dest[i++] = 0;
|
||||
}
|
||||
else
|
||||
dest[i++] = cmd;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,10 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="Blowfish.cs" />
|
||||
<Compile Include="BlowfishKeyProvider.cs" />
|
||||
<Compile Include="Dune2ShpReader.cs" />
|
||||
<Compile Include="FileSystem.cs" />
|
||||
<Compile Include="Folder.cs" />
|
||||
<Compile Include="Format2.cs" />
|
||||
<Compile Include="Format40.cs" />
|
||||
<Compile Include="Format80.cs" />
|
||||
<Compile Include="IniFile.cs" />
|
||||
|
||||
34
OpenRa.Game/Graphics/CursorSheetBuilder.cs
Normal file
34
OpenRa.Game/Graphics/CursorSheetBuilder.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenRa.FileFormats;
|
||||
|
||||
namespace OpenRa.Game.Graphics
|
||||
{
|
||||
static class CursorSheetBuilder
|
||||
{
|
||||
static Dictionary<string, Sprite[]> cursors =
|
||||
new Dictionary<string, Sprite[]>();
|
||||
|
||||
public static Sprite LoadSprite(string filename, params string[] exts)
|
||||
{
|
||||
return LoadAllSprites(filename, exts)[0];
|
||||
}
|
||||
|
||||
public static Sprite[] LoadAllSprites(string filename, params string[] exts)
|
||||
{
|
||||
Sprite[] value;
|
||||
if (!cursors.TryGetValue(filename, out value))
|
||||
{
|
||||
Dune2ShpReader shp = new Dune2ShpReader(FileSystem.OpenWithExts(filename, exts));
|
||||
value = new Sprite[shp.ImageCount];
|
||||
for (int i = 0; i < shp.ImageCount; i++)
|
||||
value[i] = SheetBuilder.Add(shp[i].Image, shp[i].Size);
|
||||
cursors.Add(filename, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,7 @@
|
||||
<Compile Include="GameRules\UnitInfo.cs" />
|
||||
<Compile Include="Graphics\Animation.cs" />
|
||||
<Compile Include="Game.cs" />
|
||||
<Compile Include="Graphics\CursorSheetBuilder.cs" />
|
||||
<Compile Include="Graphics\LineRenderer.cs" />
|
||||
<Compile Include="Graphics\OverlayRenderer.cs" />
|
||||
<Compile Include="Graphics\WorldRenderer.cs" />
|
||||
|
||||
@@ -1,222 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using OpenRa.FileFormats;
|
||||
using OpenRa.Game.Graphics;
|
||||
using OpenRa.TechTree;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRa.Game
|
||||
{
|
||||
using GRegion = OpenRa.Game.Graphics.Region;
|
||||
|
||||
class Sidebar
|
||||
{
|
||||
TechTree.TechTree techTree;
|
||||
|
||||
SpriteRenderer spriteRenderer, clockRenderer;
|
||||
Sprite blank;
|
||||
Game game;
|
||||
readonly GRegion region;
|
||||
|
||||
public GRegion Region { get { return region; } }
|
||||
public float Width { get { return spriteWidth * 2; } }
|
||||
|
||||
Dictionary<string, Sprite> sprites = new Dictionary<string,Sprite>();
|
||||
const int spriteWidth = 64, spriteHeight = 48;
|
||||
|
||||
static string[] groups = new string[] { "building", "vehicle", "boat", "infantry", "plane" };
|
||||
|
||||
Dictionary<string, string> itemGroups = new Dictionary<string,string>(); //item->group
|
||||
Dictionary<string, Animation> clockAnimations = new Dictionary<string,Animation>(); //group->clockAnimation
|
||||
Dictionary<string, SidebarItem> selectedItems = new Dictionary<string,SidebarItem>(); //group->selectedItem
|
||||
|
||||
List<SidebarItem> items = new List<SidebarItem>();
|
||||
|
||||
public Sidebar( Renderer renderer, Game game )
|
||||
{
|
||||
this.techTree = game.LocalPlayer.TechTree;
|
||||
this.techTree.BuildableItemsChanged += PopulateItemList;
|
||||
this.game = game;
|
||||
region = GRegion.Create(game.viewport, DockStyle.Right, 128, Paint, MouseHandler);
|
||||
game.viewport.AddRegion( region );
|
||||
spriteRenderer = new SpriteRenderer(renderer, false);
|
||||
clockRenderer = new SpriteRenderer(renderer, true);
|
||||
|
||||
LoadSprites("buildings.txt");
|
||||
LoadSprites("vehicles.txt");
|
||||
LoadSprites("infantry.txt");
|
||||
|
||||
foreach (string s in groups)
|
||||
{
|
||||
clockAnimations.Add(s, new Animation("clock"));
|
||||
clockAnimations[s].PlayRepeating("idle");
|
||||
selectedItems.Add(s, null);
|
||||
}
|
||||
|
||||
blank = SheetBuilder.Add(new Size((int)spriteWidth, (int)spriteHeight), 16);
|
||||
}
|
||||
|
||||
public void Build(SidebarItem item)
|
||||
{
|
||||
if (item != null)
|
||||
game.controller.orderGenerator = new PlaceBuilding(game.LocalPlayer, item.techTreeItem.tag.ToLowerInvariant());
|
||||
}
|
||||
|
||||
void LoadSprites(string filename)
|
||||
{
|
||||
foreach (string line in Util.ReadAllLines(FileSystem.Open(filename)))
|
||||
{
|
||||
string key = line.Substring(0, line.IndexOf(','));
|
||||
int secondComma = line.IndexOf(',', line.IndexOf(',') + 1);
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using OpenRa.FileFormats;
|
||||
using OpenRa.Game.Graphics;
|
||||
using OpenRa.TechTree;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRa.Game
|
||||
{
|
||||
using GRegion = OpenRa.Game.Graphics.Region;
|
||||
|
||||
class Sidebar
|
||||
{
|
||||
TechTree.TechTree techTree;
|
||||
|
||||
SpriteRenderer spriteRenderer, clockRenderer;
|
||||
Sprite blank;
|
||||
Game game;
|
||||
readonly GRegion region;
|
||||
|
||||
public GRegion Region { get { return region; } }
|
||||
public float Width { get { return spriteWidth * 2; } }
|
||||
|
||||
Dictionary<string, Sprite> sprites = new Dictionary<string,Sprite>();
|
||||
const int spriteWidth = 64, spriteHeight = 48;
|
||||
|
||||
static string[] groups = new string[] { "building", "vehicle", "boat", "infantry", "plane" };
|
||||
|
||||
Dictionary<string, string> itemGroups = new Dictionary<string,string>(); //item->group
|
||||
Dictionary<string, Animation> clockAnimations = new Dictionary<string,Animation>(); //group->clockAnimation
|
||||
Dictionary<string, SidebarItem> selectedItems = new Dictionary<string,SidebarItem>(); //group->selectedItem
|
||||
|
||||
List<SidebarItem> items = new List<SidebarItem>();
|
||||
|
||||
public Sidebar( Renderer renderer, Game game )
|
||||
{
|
||||
this.techTree = game.LocalPlayer.TechTree;
|
||||
this.techTree.BuildableItemsChanged += PopulateItemList;
|
||||
this.game = game;
|
||||
region = GRegion.Create(game.viewport, DockStyle.Right, 128, Paint, MouseHandler);
|
||||
game.viewport.AddRegion( region );
|
||||
spriteRenderer = new SpriteRenderer(renderer, false);
|
||||
clockRenderer = new SpriteRenderer(renderer, true);
|
||||
|
||||
LoadSprites("buildings.txt");
|
||||
LoadSprites("vehicles.txt");
|
||||
LoadSprites("infantry.txt");
|
||||
|
||||
foreach (string s in groups)
|
||||
{
|
||||
clockAnimations.Add(s, new Animation("clock"));
|
||||
clockAnimations[s].PlayRepeating("idle");
|
||||
selectedItems.Add(s, null);
|
||||
}
|
||||
|
||||
blank = SheetBuilder.Add(new Size((int)spriteWidth, (int)spriteHeight), 16);
|
||||
}
|
||||
|
||||
public void Build(SidebarItem item)
|
||||
{
|
||||
if (item != null)
|
||||
game.controller.orderGenerator = new PlaceBuilding(game.LocalPlayer, item.techTreeItem.tag.ToLowerInvariant());
|
||||
}
|
||||
|
||||
void LoadSprites(string filename)
|
||||
{
|
||||
foreach (string line in Util.ReadAllLines(FileSystem.Open(filename)))
|
||||
{
|
||||
string key = line.Substring(0, line.IndexOf(','));
|
||||
int secondComma = line.IndexOf(',', line.IndexOf(',') + 1);
|
||||
string group = line.Substring(secondComma + 1, line.Length - secondComma - 1);
|
||||
sprites.Add( key, SpriteSheetBuilder.LoadSprite( key + "icon", ".shp" ) );
|
||||
itemGroups.Add(key, group);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSprite(Sprite s, ref float2 p)
|
||||
{
|
||||
spriteRenderer.DrawSprite(s, p, 0);
|
||||
p.Y += spriteHeight;
|
||||
}
|
||||
|
||||
void Fill(float height, float2 p)
|
||||
{
|
||||
while (p.Y < height)
|
||||
DrawSprite(blank, ref p);
|
||||
}
|
||||
|
||||
int buildPos = 0;
|
||||
int unitPos = 0;
|
||||
|
||||
void PopulateItemList()
|
||||
{
|
||||
buildPos = 0; unitPos = 0;
|
||||
|
||||
items.Clear();
|
||||
|
||||
foreach (Item i in techTree.BuildableItems)
|
||||
{
|
||||
Sprite sprite;
|
||||
if (!sprites.TryGetValue(i.tag, out sprite)) continue;
|
||||
|
||||
items.Add(new SidebarItem(sprite, i, i.IsStructure ? buildPos : unitPos));
|
||||
|
||||
if (i.IsStructure)
|
||||
buildPos += spriteHeight;
|
||||
else
|
||||
unitPos += spriteHeight;
|
||||
}
|
||||
|
||||
foreach (string g in groups) selectedItems[g] = null;
|
||||
}
|
||||
|
||||
void Paint()
|
||||
{
|
||||
foreach (SidebarItem i in items)
|
||||
i.Paint(spriteRenderer, region.Location);
|
||||
|
||||
Fill(region.Size.Y + region.Location.Y, new float2(region.Location.X, buildPos + region.Location.Y));
|
||||
Fill(region.Size.Y + region.Location.Y, new float2(region.Location.X + spriteWidth, unitPos + region.Location.Y));
|
||||
|
||||
spriteRenderer.Flush();
|
||||
|
||||
foreach (var kvp in selectedItems)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
clockRenderer.DrawSprite(clockAnimations[kvp.Key].Image, region.Location.ToFloat2() + kvp.Value.location, 0);
|
||||
clockAnimations[kvp.Key].Tick(1);
|
||||
}
|
||||
}
|
||||
|
||||
clockRenderer.Flush();
|
||||
}
|
||||
|
||||
public SidebarItem GetItem(float2 point)
|
||||
{
|
||||
foreach (SidebarItem i in items)
|
||||
if (i.Clicked(point))
|
||||
return i;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void MouseHandler(MouseInput mi)
|
||||
{
|
||||
if (mi.Button == MouseButtons.Left && mi.Event == MouseInputEvent.Down)
|
||||
{
|
||||
var point = new float2(mi.Location.X, mi.Location.Y);
|
||||
var item = GetItem(point);
|
||||
if (item != null)
|
||||
{
|
||||
string group = itemGroups[item.techTreeItem.tag];
|
||||
if (selectedItems[group] == null)
|
||||
{
|
||||
selectedItems[group] = item;
|
||||
Build(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( mi.Button == MouseButtons.Right && mi.Event == MouseInputEvent.Down )
|
||||
{
|
||||
var point = new float2(mi.Location.X, mi.Location.Y);
|
||||
var item = GetItem(point);
|
||||
if( item != null )
|
||||
{
|
||||
string group = itemGroups[ item.techTreeItem.tag ];
|
||||
selectedItems[ group ] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlaceBuilding : IOrderGenerator
|
||||
{
|
||||
public readonly Player Owner;
|
||||
public readonly string Name;
|
||||
|
||||
public PlaceBuilding( Player owner, string name )
|
||||
{
|
||||
Owner = owner;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public IEnumerable<Order> Order( Game game, int2 xy )
|
||||
{
|
||||
// todo: check that space is free
|
||||
yield return new PlaceBuildingOrder( this, xy );
|
||||
}
|
||||
|
||||
public void PrepareOverlay(Game game, int2 xy)
|
||||
{
|
||||
game.worldRenderer.uiOverlay.SetCurrentOverlay(xy, Name);
|
||||
}
|
||||
}
|
||||
|
||||
class PlaceBuildingOrder : Order
|
||||
{
|
||||
PlaceBuilding building;
|
||||
int2 xy;
|
||||
|
||||
public PlaceBuildingOrder(PlaceBuilding building, int2 xy)
|
||||
{
|
||||
this.building = building;
|
||||
this.xy = xy;
|
||||
}
|
||||
|
||||
public override void Apply(Game game)
|
||||
{
|
||||
game.world.AddFrameEndTask(_ =>
|
||||
{
|
||||
Log.Write( "Player \"{0}\" builds {1}", building.Owner.PlayerName, building.Name );
|
||||
game.world.Add( new Actor( building.Name, xy, building.Owner ) );
|
||||
|
||||
game.controller.orderGenerator = null;
|
||||
game.worldRenderer.uiOverlay.KillOverlay();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
sprites.Add( key, SpriteSheetBuilder.LoadSprite( key + "icon", ".shp" ) );
|
||||
itemGroups.Add(key, group);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSprite(Sprite s, ref float2 p)
|
||||
{
|
||||
spriteRenderer.DrawSprite(s, p, 0);
|
||||
p.Y += spriteHeight;
|
||||
}
|
||||
|
||||
void Fill(float height, float2 p)
|
||||
{
|
||||
while (p.Y < height)
|
||||
DrawSprite(blank, ref p);
|
||||
}
|
||||
|
||||
int buildPos = 0;
|
||||
int unitPos = 0;
|
||||
|
||||
void PopulateItemList()
|
||||
{
|
||||
buildPos = 0; unitPos = 0;
|
||||
|
||||
items.Clear();
|
||||
|
||||
foreach (Item i in techTree.BuildableItems)
|
||||
{
|
||||
Sprite sprite;
|
||||
if (!sprites.TryGetValue(i.tag, out sprite)) continue;
|
||||
|
||||
items.Add(new SidebarItem(sprite, i, i.IsStructure ? buildPos : unitPos));
|
||||
|
||||
if (i.IsStructure)
|
||||
buildPos += spriteHeight;
|
||||
else
|
||||
unitPos += spriteHeight;
|
||||
}
|
||||
|
||||
foreach (string g in groups) selectedItems[g] = null;
|
||||
}
|
||||
|
||||
void Paint()
|
||||
{
|
||||
foreach (SidebarItem i in items)
|
||||
i.Paint(spriteRenderer, region.Location);
|
||||
|
||||
Fill(region.Size.Y + region.Location.Y, new float2(region.Location.X, buildPos + region.Location.Y));
|
||||
Fill(region.Size.Y + region.Location.Y, new float2(region.Location.X + spriteWidth, unitPos + region.Location.Y));
|
||||
|
||||
spriteRenderer.Flush();
|
||||
|
||||
foreach (var kvp in selectedItems)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
clockRenderer.DrawSprite(clockAnimations[kvp.Key].Image, region.Location.ToFloat2() + kvp.Value.location, 0);
|
||||
clockAnimations[kvp.Key].Tick(1);
|
||||
}
|
||||
}
|
||||
|
||||
clockRenderer.Flush();
|
||||
}
|
||||
|
||||
public SidebarItem GetItem(float2 point)
|
||||
{
|
||||
foreach (SidebarItem i in items)
|
||||
if (i.Clicked(point))
|
||||
return i;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void MouseHandler(MouseInput mi)
|
||||
{
|
||||
if (mi.Button == MouseButtons.Left && mi.Event == MouseInputEvent.Down)
|
||||
{
|
||||
var point = new float2(mi.Location.X, mi.Location.Y);
|
||||
var item = GetItem(point);
|
||||
if (item != null)
|
||||
{
|
||||
string group = itemGroups[item.techTreeItem.tag];
|
||||
if (selectedItems[group] == null)
|
||||
{
|
||||
selectedItems[group] = item;
|
||||
Build(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( mi.Button == MouseButtons.Right && mi.Event == MouseInputEvent.Down )
|
||||
{
|
||||
var point = new float2(mi.Location.X, mi.Location.Y);
|
||||
var item = GetItem(point);
|
||||
if( item != null )
|
||||
{
|
||||
string group = itemGroups[ item.techTreeItem.tag ];
|
||||
selectedItems[ group ] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlaceBuilding : IOrderGenerator
|
||||
{
|
||||
public readonly Player Owner;
|
||||
public readonly string Name;
|
||||
|
||||
public PlaceBuilding( Player owner, string name )
|
||||
{
|
||||
Owner = owner;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public IEnumerable<Order> Order( Game game, int2 xy )
|
||||
{
|
||||
// todo: check that space is free
|
||||
yield return new PlaceBuildingOrder( this, xy );
|
||||
}
|
||||
|
||||
public void PrepareOverlay(Game game, int2 xy)
|
||||
{
|
||||
game.worldRenderer.uiOverlay.SetCurrentOverlay(xy, Name);
|
||||
}
|
||||
}
|
||||
|
||||
class PlaceBuildingOrder : Order
|
||||
{
|
||||
PlaceBuilding building;
|
||||
int2 xy;
|
||||
|
||||
public PlaceBuildingOrder(PlaceBuilding building, int2 xy)
|
||||
{
|
||||
this.building = building;
|
||||
this.xy = xy;
|
||||
}
|
||||
|
||||
public override void Apply(Game game)
|
||||
{
|
||||
game.world.AddFrameEndTask(_ =>
|
||||
{
|
||||
Log.Write( "Player \"{0}\" builds {1}", building.Owner.PlayerName, building.Name );
|
||||
game.world.Add( new Actor( building.Name, xy, building.Owner ) );
|
||||
|
||||
game.controller.orderGenerator = null;
|
||||
game.worldRenderer.uiOverlay.KillOverlay();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
57
doc/shp dune 2.txt
Normal file
57
doc/shp dune 2.txt
Normal file
@@ -0,0 +1,57 @@
|
||||
======================
|
||||
Dune 2 SHP file format
|
||||
======================
|
||||
Sourced from Red Horizon Utilities by Emanuel Rabina
|
||||
http://www.ultraq.net.nz/redhorizon/
|
||||
|
||||
The Dune 2 SHP file, is a multiple image filetype, where each image can have
|
||||
it's own set of dimensions. The file is structured as follows:
|
||||
|
||||
File header:
|
||||
NumImages (2 bytes) - the number of images in the file
|
||||
Offsets[NumImages + 1] - offset to the image header for an image. The last
|
||||
(2 or 4 bytes each) offset points to the end of the file. The offsets
|
||||
don't take into account the NumImages bytes at the
|
||||
beginning, so add 2 bytes to the offset value to
|
||||
get the actual position of an image header in the
|
||||
file
|
||||
|
||||
The size of the offsets can be either 2 or 4 bytes. There is no simple way
|
||||
to determine which it will be, but checking the file's 4th byte to see if
|
||||
it's 0, seems to be a commonly accepted practice amongst existing Dune 2 SHP
|
||||
file readers:
|
||||
|
||||
eg: A 2-byte offset file: 01 00 06 00 EC 00 45 0A ...
|
||||
A 4-byte offset file: 01 00 08 00 00 00 EC 00 ...
|
||||
^^
|
||||
The marked byte will be 0 in 4-byte offset files, non 0 in 2-byte offset
|
||||
files.
|
||||
Lastly, like C&C SHP files, there is an extra offset, pointing to the end of
|
||||
the file (or what would have been the position of another image header/data
|
||||
pair).
|
||||
|
||||
Following the file header, are a series of image header & image data pairs.
|
||||
The image header is structured as follows:
|
||||
|
||||
Image header:
|
||||
Flags (2 bytes) - flags to identify the type of data following the
|
||||
Datasize field, and/or the compression schemes used
|
||||
Slices (1 byte) - number of Format2 slices used to encode the image
|
||||
data. Often this is the same as the height of the
|
||||
image
|
||||
Width (2 bytes) - width of the image
|
||||
Height (1 byte) - height of the image
|
||||
Filesize (2 bytes) - size of the image data in the file
|
||||
Datasize (2 bytes) - size of the image data when Format2 encoded/compressed
|
||||
|
||||
Regarding the flags, there seem to be 4 values:
|
||||
- 00000000 (0) = Decompress with Format80, then Format2
|
||||
- 00000001 (1) = (see 3)
|
||||
- 00000010 (2) = Decompress with Format2
|
||||
- 00000011 (3) = A small 16-byte lookup table follows, and the image data
|
||||
should be decompressed with Format80 then Format2.
|
||||
- 00000101 (5) = The first byte gives the size of the lookup table that
|
||||
follows, and the image data should be decompressed with
|
||||
Format80 then Format2.
|
||||
|
||||
And after this image header is the image data.
|
||||
Reference in New Issue
Block a user