1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-20 14:15:23 +00:00

Initial commit for animation decoding (not finished). Minor fixups.

This commit is contained in:
Tim Sarbin 2018-12-05 22:29:45 -05:00
parent 4a77e47ddd
commit f2c14375cf
22 changed files with 1007 additions and 17 deletions

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Enums
{
public enum eAnimationFrame
{
NoEvent,
Attack,
Missile,
Sound,
Skill
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Enums
{
public enum eArmorType
{
Lite,
Medium,
Heavy
}
public static class eArmorTypeHelper
{
private static readonly Dictionary<eArmorType, string> tokens = new Dictionary<eArmorType, string>()
{
{ eArmorType.Lite , "lit" },
{ eArmorType.Medium , "med" },
{ eArmorType.Heavy , "hvy" }
};
public static string ToToken(this eArmorType armorType) => tokens[armorType];
public static eArmorType ToArmorType(this string source) => tokens.First(x => x.Value.ToUpper() == source.ToUpper()).Key;
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Enums
{
public enum eCompositType
{
Head,
Torso,
Legs,
RightArm,
LeftArm,
RightHand,
LeftHand,
Shield,
Special1,
Special2,
Special3,
Special4,
Special5,
Special6,
Special7,
Special8
}
public static class eCompositeTypeHelper
{
private readonly static Dictionary<eCompositType, string> tokens = new Dictionary<eCompositType, string>
{
{ eCompositType.Head , "HD" },
{ eCompositType.Torso , "TR" },
{ eCompositType.Legs , "LG" },
{ eCompositType.RightArm , "RA" },
{ eCompositType.LeftArm , "LA" },
{ eCompositType.RightHand , "RH" },
{ eCompositType.LeftHand , "LH" },
{ eCompositType.Shield , "SH" },
{ eCompositType.Special1 , "S1" },
{ eCompositType.Special2 , "S2" },
{ eCompositType.Special3 , "S3" },
{ eCompositType.Special4 , "S4" },
{ eCompositType.Special5 , "S5" },
{ eCompositType.Special6 , "S6" },
{ eCompositType.Special7 , "S7" },
{ eCompositType.Special8 , "S8" }
};
public static string ToToken(this eCompositType source) => tokens[source];
public static eCompositType ToCompositeType(this string source) => tokens.First(x => x.Value.ToUpper() == source.ToUpper()).Key;
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Enums
{
public enum eDrawEffect
{
//75 % transparency (colormaps 561-816 in a .pl2)
PctTransparency75,
//50 % transparency (colormaps 305-560 in a .pl2)
PctTransparency50,
//25 % transparency (colormaps 49-304 in a .pl2)
PctTransparency25,
//Screen (colormaps 817-1072 in a .pl2)
Screen,
//luminance (colormaps 1073-1328 in a .pl2)
Luminance,
//bright alpha blending (colormaps 1457-1712 in a .pl2)
BringAlphaBlending
}
}

View File

@ -16,4 +16,21 @@ namespace OpenDiablo2.Common.Enums
Amazon,
Druid
}
public static class eHeroExtensions
{
public readonly static Dictionary<eHero, string> tokens = new Dictionary<eHero, string>
{
{ eHero.Amazon , "AM" },
{ eHero.Sorceress , "SO" },
{ eHero.Necromancer , "NE" },
{ eHero.Paladin , "PA" },
{ eHero.Barbarian , "BA" },
{ eHero.Druid , "DZ" },
{ eHero.Assassin , "AI" }
};
public static string ToToken(this eHero source) => tokens[source];
public static eHero ToHero(this string source) => tokens.First(x => x.Value.ToUpper() == source.ToUpper()).Key;
}
}

View File

@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Enums
{
public enum eMobMode
{
PlayerDeath,
PlayerNeutral,
PlayerWalk,
PlayerRun,
PlayerGetHit,
PlayerTownNeutral,
PlayerTownWalk,
PlayerAttack1,
PlayerAttack2,
PlayerBlock,
PlayerCast,
PlayerThrow,
PlayerKick,
PlayerSkill1,
PlayerSkill2,
PlayerSkill3,
PlayerSkill4,
PlayerDead,
PlayerSequence,
PlayerKnockBack,
MonsterDeath,
MonsterNeutral,
MonsterWalk,
MonsterGetHit,
MonsterAttack1,
MonsterAttack2,
MonsterBlock,
MonsterCast,
MonsterSkill1,
MonsterSkill2,
MonsterSkill3,
MonsterSkill4,
MonsterDead,
MonsterKnockback,
MonsterSequence,
MonsterRun,
ObjectNeutral,
ObjectOperating,
ObjectOpened,
ObjectSpecial1,
ObjectSpecial2,
ObjectSpecial3,
ObjectSpecial4,
ObjectSpecial5
}
public static class eMobModeExtensions
{
private static readonly Dictionary<eMobMode, string> mobModes = new Dictionary<eMobMode, string>
{
{ eMobMode.PlayerDeath ,"DT" },
{ eMobMode.PlayerNeutral ,"NU" },
{ eMobMode.PlayerWalk ,"WL" },
{ eMobMode.PlayerRun ,"RN" },
{ eMobMode.PlayerGetHit ,"GH" },
{ eMobMode.PlayerTownNeutral ,"TN" },
{ eMobMode.PlayerTownWalk ,"TW" },
{ eMobMode.PlayerAttack1 ,"A1" },
{ eMobMode.PlayerAttack2 ,"A2" },
{ eMobMode.PlayerBlock ,"BL" },
{ eMobMode.PlayerCast ,"SC" },
{ eMobMode.PlayerThrow ,"TH" },
{ eMobMode.PlayerKick ,"KK" },
{ eMobMode.PlayerSkill1 ,"S1" },
{ eMobMode.PlayerSkill2 ,"S2" },
{ eMobMode.PlayerSkill3 ,"S3" },
{ eMobMode.PlayerSkill4 ,"S4" },
{ eMobMode.PlayerDead ,"DD" },
{ eMobMode.PlayerSequence ,"GH" },
{ eMobMode.PlayerKnockBack ,"GH" },
{ eMobMode.MonsterDeath , "DT" },
{ eMobMode.MonsterNeutral , "NU" },
{ eMobMode.MonsterWalk , "WL" },
{ eMobMode.MonsterGetHit , "GH" },
{ eMobMode.MonsterAttack1 , "A1" },
{ eMobMode.MonsterAttack2 , "A2" },
{ eMobMode.MonsterBlock , "BL" },
{ eMobMode.MonsterCast , "SC" },
{ eMobMode.MonsterSkill1 , "S1" },
{ eMobMode.MonsterSkill2 , "S2" },
{ eMobMode.MonsterSkill3 , "S3" },
{ eMobMode.MonsterSkill4 , "S4" },
{ eMobMode.MonsterDead , "DD" },
{ eMobMode.MonsterKnockback , "GH" },
{ eMobMode.MonsterSequence , "xx" },
{ eMobMode.MonsterRun , "RN" },
{ eMobMode.ObjectNeutral , "NU" },
{ eMobMode.ObjectOperating , "OP" },
{ eMobMode.ObjectOpened , "ON" },
{ eMobMode.ObjectSpecial1 , "S1" },
{ eMobMode.ObjectSpecial2 , "S2" },
{ eMobMode.ObjectSpecial3 , "S3" },
{ eMobMode.ObjectSpecial4 , "S4" },
{ eMobMode.ObjectSpecial5 , "S5" }
};
public static string ToToken(this eMobMode src) => mobModes[src];
public static eMobMode FromToken(this string token) => mobModes.First(x => x.Value.ToUpper() == token.ToUpper()).Key;
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Enums
{
public enum eWeaponClass
{
None,
HandToHand,
Bow,
OneHandSwing,
OneHandThrust,
Staff,
TwoHandSwing,
TwoHandThrust,
Crossbow,
LeftJabRightSwing,
LeftJabRightThrust,
LeftSwingRightSwing,
LeftSwingRightThrust,
OneHandToHand,
TwoHandToHand
}
public static class eWeaponClassExtensions
{
private static readonly Dictionary<eWeaponClass, string> codes = new Dictionary<eWeaponClass, string>
{
{eWeaponClass.None , "" },
{eWeaponClass.HandToHand , "hth" },
{eWeaponClass.Bow , "bow" },
{eWeaponClass.OneHandSwing , "1hs" },
{eWeaponClass.OneHandThrust , "1ht" },
{eWeaponClass.Staff , "stf" },
{eWeaponClass.TwoHandSwing , "2hs" },
{eWeaponClass.TwoHandThrust , "2ht" },
{eWeaponClass.Crossbow , "xbw" },
{eWeaponClass.LeftJabRightSwing , "1js" },
{eWeaponClass.LeftJabRightThrust , "1jt" },
{eWeaponClass.LeftSwingRightSwing , "1ss" },
{eWeaponClass.LeftSwingRightThrust , "1st" },
{eWeaponClass.OneHandToHand , "ht1" },
{eWeaponClass.TwoHandToHand , "ht2" }
};
public static string ToToken(this eWeaponClass source) => codes[source];
public static eWeaponClass ToWeaponClass(this string source) => codes.First(x => x.Value.ToUpper() == source.ToUpper()).Key;
}
}

View File

@ -1,4 +1,6 @@
using OpenDiablo2.Common.Models;
using System.Collections.Generic;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Models;
namespace OpenDiablo2.Common.Interfaces
{
@ -17,5 +19,9 @@ namespace OpenDiablo2.Common.Interfaces
MPQDS1 GetMPQDS1(string resourcePath, LevelPreset level, LevelDetail levelDetail, LevelType levelType);
MPQDT1 GetMPQDT1(string resourcePath);
Palette GetPalette(string paletteName);
MPQCOF GetPlayerAnimation(eHero hero, eWeaponClass weaponClass, eMobMode mobMode);
MPQDCC GetPlayerDCC(MPQCOF.COFLayer cofLayer, eArmorType armorType, Palette palette);
Dictionary<string, List<AnimationData>> Animations { get; }
}
}

View File

@ -10,8 +10,6 @@ namespace OpenDiablo2.Common.Interfaces
public interface ISessionManager : ISessionEventProvider, IDisposable
{
Guid PlayerId { get; }
void Initialize();
void Stop();

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Models
{
public sealed class AnimationData
{
public string COFName { get; set; }
public int FramesPerDirection { get; set; }
public int AnimationSpeed { get; set; }
public byte[] Flags { get; set; }
public static Dictionary<string, List<AnimationData>> LoadFromStream(Stream stream)
{
var result = new Dictionary<string, List<AnimationData>>();
var br = new BinaryReader(stream);
while(stream.Length > stream.Position)
{
var dataCount = br.ReadInt32();
for (int i = 0; i < dataCount; ++i)
{
var data = new AnimationData
{
COFName = Encoding.ASCII.GetString(br.ReadBytes(8)).Trim('\0'),
FramesPerDirection = br.ReadInt32(),
AnimationSpeed = br.ReadInt32(),
Flags = br.ReadBytes(144),
};
if (!result.ContainsKey(data.COFName.ToUpper()))
result[data.COFName.ToUpper()] = new List<AnimationData>();
result[data.COFName.ToUpper()].Add(data);
}
}
br.Dispose();
return result;
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.IO;
namespace OpenDiablo2.Common.Models
{
public sealed class BitMuncher
{
private readonly byte[] data;
public int Offset { get; private set; }
public int BitsRead { get; private set; }
public BitMuncher(byte[] data, int offset = 0)
{
this.data = data;
this.Offset = offset;
}
public BitMuncher(BitMuncher source)
{
this.data = source.data;
this.Offset = source.Offset;
}
public int GetBit()
{
var result = (data[Offset / 8] >> (Offset % 8)) & 0x01;
Offset++;
BitsRead++;
return result;
}
public void SkipBits(int bits) => Offset += bits;
public byte GetByte() => (byte)GetBits(8);
public Int32 GetInt32() => MakeSigned(GetBits(32), 32);
public int GetBits(int bits)
{
var result = 0;
for (var i = 0; i < bits; i++)
result |= (GetBit() << i);
return result;
}
public int GetSignedBits(int bits) => MakeSigned(GetBits(bits), bits);
private int MakeSigned(int value, int bits)
{
var msbSet = ((value & (1 << (bits - 1))) != 0);
if (msbSet)
{
value = ~value + 1;
}
return value;
}
}
}

View File

@ -30,7 +30,7 @@ namespace OpenDiablo2.Common.Models
{
var i = x + (y * Width);
if (i >= ImageData.Length)
return 0;
return 0;
var index = ImageData[i];
if (index == -1)
@ -44,10 +44,6 @@ namespace OpenDiablo2.Common.Models
{
static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private UInt32 version;
private UInt32 unknown1; // 01 00 00 00 ???
private UInt32 unknown2; // 00 00 00 00 ???
private UInt32 termination; // EE EE EE EE or CD CD CD CD ???
private UInt32[] framePointers;
public ImageFrame[] Frames { get; private set; }
@ -57,12 +53,13 @@ namespace OpenDiablo2.Common.Models
public static ImageSet LoadFromStream(Stream stream)
{
var br = new BinaryReader(stream);
var version = br.ReadUInt32();
var unknown1 = br.ReadUInt32();
var unknown2 = br.ReadUInt32();
var termination = br.ReadUInt32();
var result = new ImageSet
{
version = br.ReadUInt32(),
unknown1 = br.ReadUInt32(),
unknown2 = br.ReadUInt32(),
termination = br.ReadUInt32(),
Directions = br.ReadUInt32(),
FramesPerDirection = br.ReadUInt32()
};

View File

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenDiablo2.Common.Enums;
namespace OpenDiablo2.Common.Models
{
public sealed class MPQCOF
{
public class COFLayer
{
public MPQCOF COF { get; internal set; }
public eCompositType CompositType { get; internal set; }
public byte Shadow { get; internal set; }
public bool IsTransparent { get; internal set; }
public eDrawEffect DrawEffect { get; internal set; }
public eWeaponClass WeaponClass { get; internal set; }
public string GetDCCPath(eArmorType armorType)
=> $"{ResourcePaths.PlayerAnimationBase}\\{COF.Hero.ToToken()}\\{CompositType.ToToken()}\\{COF.Hero.ToToken()}{CompositType.ToToken()}{armorType.ToToken()}{COF.MobMode.ToToken()}{COF.WeaponClass.ToToken()}.dcc";
}
public eHero Hero { get; private set; }
public eWeaponClass WeaponClass { get; private set; }
public eMobMode MobMode { get; private set; }
public List<AnimationData> Animations { get; private set; }
public IEnumerable<COFLayer> Layers { get; private set; }
public IEnumerable<eAnimationFrame> AnimationFrames { get; private set; }
public static MPQCOF Load(Stream stream, Dictionary<string, List<AnimationData>> animations, eHero hero, eWeaponClass weaponClass, eMobMode mobMode)
{
var result = new MPQCOF
{
WeaponClass = weaponClass,
MobMode = mobMode,
Hero = hero
};
var br = new BinaryReader(stream);
var numLayers = br.ReadByte();
var framesPerDir = br.ReadByte();
var numDirections = br.ReadByte();
br.ReadBytes(25); // Skip 25 unknown bytes...
var layers = new List<COFLayer>();
for (var layerIdx = 0; layerIdx < numLayers; layerIdx++)
{
var layer = new COFLayer();
layer.COF = result;
layer.CompositType = (eCompositType)br.ReadByte();
layer.Shadow = br.ReadByte();
br.ReadByte(); // Unknown
layer.IsTransparent = br.ReadByte() != 0;
layer.DrawEffect = (eDrawEffect)br.ReadByte();
layers.Add(layer);
layer.WeaponClass = Encoding.ASCII.GetString(br.ReadBytes(4)).Trim('\0').ToWeaponClass();
}
result.Layers = layers;
result.AnimationFrames = br.ReadBytes(framesPerDir).Select(x => (eAnimationFrame)x);
var cofName = $"{hero.ToToken()}{mobMode.ToToken()}{weaponClass.ToToken()}".ToUpper();
result.Animations = animations[cofName];
br.Dispose();
return result;
}
}
}

View File

@ -0,0 +1,440 @@
using System;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using System.Drawing;
namespace OpenDiablo2.Common.Models
{
public sealed class MPQDCC
{
public struct PixelBufferEntry
{
public byte[] Value;
public int Frame;
public int FrameCellIndex;
}
public struct Cell
{
public int Width;
public int Height;
public int XOffset;
public int YOffset;
}
public sealed class MPQDCCDirectionFrame
{
public int Width { get; private set; }
public int Height { get; private set; }
public int XOffset { get; private set; }
public int YOffset { get; private set; }
public int NumberOfOptionalBytes { get; private set; }
public int NumberOfCodedBytes { get; private set; }
public bool FrameIsBottomUp { get; private set; }
public Rectangle Box { get; private set; }
public Cell[] Cells { get; private set; }
public int HorizontalCellCount { get; private set; }
public int VerticalCellCount { get; private set; }
public MPQDCCDirectionFrame(BitMuncher bits, MPQDCCDirection direction)
{
var variable0 = bits.GetBits(direction.Variable0Bits);
Width = bits.GetBits(direction.WidthBits);
Height = bits.GetBits(direction.HeightBits);
XOffset = bits.GetSignedBits(direction.XOffsetBits);
YOffset = bits.GetSignedBits(direction.YOffsetBits);
NumberOfOptionalBytes = bits.GetBits(direction.OptionalDataBits);
NumberOfCodedBytes = bits.GetBits(direction.CodedBytesBits);
FrameIsBottomUp = bits.GetBit() == 1;
Box = new Rectangle(
XOffset,
YOffset - Height + 1,
Width,
Height
);
}
public void MakeCells(MPQDCCDirection direction)
{
var w = 4 - ((Box.Left - direction.Box.Left) % 4); // Width of the first column (in pixels)
if ((Box.Width - w) <= 1)
HorizontalCellCount = 1;
else
{
HorizontalCellCount = 2 + ((Box.Width - w - 1) / 4);
if (((Box.Width - w - 1) % 4) == 0)
HorizontalCellCount--;
}
var h = 4 - ((Box.Top - direction.Box.Top) % 4); // Height of the first column (in pixels)
if ((Box.Height - h) <= 1)
VerticalCellCount = 1;
else
{
VerticalCellCount = 2 + ((Box.Height - h - 1) / 4);
if (((Box.Height - h - 1) % 4) == 0)
VerticalCellCount--;
}
Cells = new Cell[HorizontalCellCount * VerticalCellCount];
// Calculate the cell widths and heights
var cellWidths = new int[HorizontalCellCount];
if (HorizontalCellCount == 1)
cellWidths[0] = Box.Width;
else
{
cellWidths[0] = w;
for (var i = 1; i < (HorizontalCellCount - 1); i++)
cellWidths[i] = 4;
cellWidths[HorizontalCellCount - 1] = Box.Width - w - (4 * (HorizontalCellCount - 2));
}
var cellHeights = new int[VerticalCellCount];
if (VerticalCellCount == 1)
cellHeights[0] = Box.Height;
else
{
cellHeights[0] = h;
for (var i = 1; i < (VerticalCellCount - 1); i++)
cellHeights[i] = 4;
cellHeights[VerticalCellCount - 1] = Box.Height - h - (4 * (VerticalCellCount - 2));
}
Cells = new Cell[HorizontalCellCount * VerticalCellCount];
var offsetY = Box.Top - direction.Box.Top;
for (var y = 0; y < VerticalCellCount; y++)
{
var offsetX = Box.Left - direction.Box.Left;
for (var x = 0; x < HorizontalCellCount; x++)
{
Cells[x + (y * HorizontalCellCount)] = new Cell
{
XOffset = offsetX,
YOffset = offsetY,
Width = cellWidths[x],
Height = cellHeights[y]
};
offsetX += cellWidths[x];
}
offsetY += cellHeights[y];
}
}
}
public sealed class MPQDCCDirection
{
public int OutSizeCoded { get; private set; }
public int CompressionFlags { get; private set; }
public int Variable0Bits { get; private set; }
public int WidthBits { get; private set; }
public int HeightBits { get; private set; }
public int XOffsetBits { get; private set; }
public int YOffsetBits { get; private set; }
public int OptionalDataBits { get; private set; }
public int CodedBytesBits { get; private set; }
public int EqualCellsBitstreamSize { get; private set; }
public int PixelMaskBitstreamSize { get; private set; }
public int EncodingTypeBitsreamSize { get; private set; }
public int RawPixelCodesBitstreamSize { get; private set; }
public MPQDCCDirectionFrame[] Frames { get; private set; }
public int[] PaletteEntries { get; private set; }
public Rectangle Box { get; private set; }
public Cell[] Cells { get; private set; }
public int HorizontalCellCount { get; private set; }
public int VerticalCellCount { get; private set; }
public List<PixelBufferEntry> PixelBuffer { get; private set; }
private static readonly byte[] crazyBitTable = { 0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 26, 28, 30, 32 };
public MPQDCCDirection(BitMuncher bm, MPQDCC file)
{
OutSizeCoded = bm.GetInt32();
CompressionFlags = bm.GetBits(2);
Variable0Bits = crazyBitTable[bm.GetBits(4)];
WidthBits = crazyBitTable[bm.GetBits(4)];
HeightBits = crazyBitTable[bm.GetBits(4)];
XOffsetBits = crazyBitTable[bm.GetBits(4)];
YOffsetBits = crazyBitTable[bm.GetBits(4)];
OptionalDataBits = crazyBitTable[bm.GetBits(4)];
CodedBytesBits = crazyBitTable[bm.GetBits(4)];
Frames = new MPQDCCDirectionFrame[file.NumberOfFrames];
if (Variable0Bits != 0)
throw new ApplicationException("Variable0Bits is not 0. This is not ok.");
// Load the frame headers
for (var frameIdx = 0; frameIdx < file.NumberOfFrames; frameIdx++)
Frames[frameIdx] = new MPQDCCDirectionFrame(bm, this);
Box = new Rectangle
{
X = Frames.Min(z => z.Box.X),
Y = Frames.Min(z => z.Box.Y),
Width = Frames.Max(z => z.Box.Right - z.Box.Left),
Height = Frames.Max(z => z.Box.Bottom - z.Box.Top)
};
if (OptionalDataBits > 0)
throw new ApplicationException("Optional bits in DCC data is not currently supported.");
if ((CompressionFlags & 0x2) > 0)
EqualCellsBitstreamSize = bm.GetBits(20);
PixelMaskBitstreamSize = bm.GetBits(20);
if ((CompressionFlags & 0x1) > 0)
{
EncodingTypeBitsreamSize = bm.GetBits(20);
RawPixelCodesBitstreamSize = bm.GetBits(20);
}
// PixelValuesKey
var paletteEntries = new List<bool>();
for (var i = 0; i < 256; i++)
paletteEntries.Add(bm.GetBit() != 0);
PaletteEntries = new int[paletteEntries.Count(x => x == true)];
var paletteOffset = 0;
for (var i = 0; i < 256; i++)
{
if (!paletteEntries[i])
continue;
PaletteEntries[paletteOffset++] = i;
}
var equalCellsBitstream = new BitMuncher(bm);
bm.SkipBits(EqualCellsBitstreamSize);
var pixelMaskBitstream = new BitMuncher(bm);
bm.SkipBits(PixelMaskBitstreamSize);
var encodingTypeBitsream = new BitMuncher(bm);
bm.SkipBits(EncodingTypeBitsreamSize);
var rawPixelCodesBitstream = new BitMuncher(bm);
bm.SkipBits(RawPixelCodesBitstreamSize);
var pixelCodeandDisplacement = new BitMuncher(bm);
CalculateCellOffsets();
foreach (var frame in Frames)
frame.MakeCells(this);
FillPixelBuffer(pixelCodeandDisplacement, equalCellsBitstream, pixelMaskBitstream, encodingTypeBitsream, rawPixelCodesBitstream);
if (equalCellsBitstream.BitsRead != EqualCellsBitstreamSize)
throw new ApplicationException("Did not read the correct number of bits!");
if (pixelMaskBitstream.BitsRead != PixelMaskBitstreamSize)
throw new ApplicationException("Did not read the correct number of bits!");
if (encodingTypeBitsream.BitsRead != EncodingTypeBitsreamSize)
throw new ApplicationException("Did not read the correct number of bits!");
if (rawPixelCodesBitstream.BitsRead != RawPixelCodesBitstreamSize)
throw new ApplicationException("Did not read the correct number of bits!");
bm.SkipBits(pixelCodeandDisplacement.BitsRead);
}
private static readonly int[] pixelMaskLookup = new int[] { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
private void FillPixelBuffer(BitMuncher pcd, BitMuncher ec, BitMuncher pm, BitMuncher et, BitMuncher rp)
{
UInt32 lastPixel = 0;
var cellBuffer = new int[HorizontalCellCount * VerticalCellCount]; // Store the offset to the cell buffer
for (var i = 0; i < cellBuffer.Length; i++)
cellBuffer[i] = -1;
PixelBuffer = new List<PixelBufferEntry>();
var frameIndex = -1;
UInt32 pixelMask = 0x00;
foreach (var frame in Frames)
{
frameIndex++;
var originCellX = (frame.Box.Left - Box.Left) / 4;
var originCellY = (frame.Box.Top - Box.Top) / 4;
for (var cellY = 0; cellY < frame.VerticalCellCount; cellY++)
{
var currentCellY = cellY + originCellY;
for (var cellX = 0; cellX < frame.HorizontalCellCount; cellX++)
{
var currentCell = (originCellX + cellX) + (currentCellY * HorizontalCellCount);
var nextCell = false;
if (cellBuffer[currentCell] != -1)
{
var tmp = 0;
if (EqualCellsBitstreamSize > 0)
tmp = ec.GetBit();
if (tmp == 0)
pixelMask = (UInt32)pm.GetBits(4);
else
nextCell = true;
}
else pixelMask = 0x0F;
if (nextCell == false)
{
// Decode the pixels
UInt32[] pixelStack = new UInt32[4];
int numberOfPixelBits = pixelMaskLookup[pixelMask];
var encodingType = ((numberOfPixelBits != 0) && (EncodingTypeBitsreamSize > 0))
? et.GetBit()
: 0;
int decodedPixel = 0;
for (int i = 0; i < numberOfPixelBits; i++)
{
if (encodingType != 0)
{
pixelStack[i] = (UInt32)rp.GetBits(8);
}
else
{
pixelStack[i] = lastPixel;
var pixelDisplacement = pcd.GetBits(4);
pixelStack[i] += (UInt32)pixelDisplacement;
while (pixelDisplacement == 15)
{
pixelDisplacement = pcd.GetBits(4);
pixelStack[i] += (UInt32)pixelDisplacement;
}
}
if (pixelStack[i] == lastPixel)
{
pixelStack[i] = 0;
i = numberOfPixelBits; // Just break here....
}
else
{
lastPixel = pixelStack[i];
decodedPixel++;
}
}
var oldEntry = cellBuffer[currentCell];
var curIdx = decodedPixel - 1;
var newEntry = new PixelBufferEntry { Value = new byte[4] };
for (int i = 0; i < 4; i++)
{
if ((pixelMask * (1 << i)) != 0)
{
if (curIdx >= 0)
newEntry.Value[i] = (byte)pixelStack[curIdx--];
else
newEntry.Value[i] = 0;
}
else
newEntry.Value[i] = PixelBuffer[oldEntry].Value[i];
}
newEntry.Frame = frameIndex;
newEntry.FrameCellIndex = XOffsetBits + (cellY * HorizontalCellCount);
PixelBuffer.Add(newEntry);
cellBuffer[currentCell] = PixelBuffer.Count() - 1;
}
}
}
}
}
private void CalculateCellOffsets()
{
// Calculate the number of vertical and horizontal cells we need
HorizontalCellCount = 1 + (Box.Width - 1) / 4;
VerticalCellCount = 1 + (Box.Height - 1) / 4;
// Calculate the cell widths
var cellWidths = new int[HorizontalCellCount];
if (HorizontalCellCount == 1)
cellWidths[0] = Box.Width;
else
{
for (var i = 0; i < HorizontalCellCount - 1; i++)
cellWidths[i] = 4;
cellWidths[HorizontalCellCount - 1] = Box.Width - (4 * (HorizontalCellCount - 1));
}
// Calculate the cell heights
var cellHeights = new int[VerticalCellCount];
if (VerticalCellCount == 1)
cellHeights[0] = Box.Height;
else
{
for (var i = 0; i < VerticalCellCount - 1; i++)
cellHeights[i] = 4;
cellHeights[VerticalCellCount - 1] = Box.Height - (4 * (VerticalCellCount - 1));
}
// Set the cell widths and heights in the cell buffer
Cells = new Cell[VerticalCellCount * HorizontalCellCount];
var yOffset = 0;
for (var y = 0; y < VerticalCellCount; y++)
{
var xOffset = 0;
for (var x = 0; x < HorizontalCellCount; x++)
{
Cells[x + (y * HorizontalCellCount)] = new Cell
{
Width = cellWidths[x],
Height = cellHeights[y],
XOffset = xOffset,
YOffset = yOffset
};
xOffset += 4;
}
yOffset += 4;
}
}
}
public int Signature { get; private set; }
public int Version { get; private set; }
public int NumberOfDirections { get; private set; }
public int NumberOfFrames { get; private set; }
public MPQDCCDirection[] Directions { get; private set; }
public MPQDCC(byte[] data, Palette palette)
{
var bm = new BitMuncher(data);
Signature = bm.GetByte();
if (Signature != 0x74)
throw new ApplicationException("Signature expected to be 0x74 but it is not.");
Version = bm.GetByte();
NumberOfDirections = bm.GetByte();
NumberOfFrames = bm.GetInt32();
if (bm.GetInt32() != 1)
throw new ApplicationException("This value isn't 1. It has to be 1.");
var totalSizeCoded = bm.GetInt32();
var directionOffsets = new int[NumberOfDirections];
for (var i = 0; i < NumberOfDirections; i++)
directionOffsets[i] = bm.GetInt32();
Directions = new MPQDCCDirection[NumberOfDirections];
for (var i = 0; i < NumberOfDirections; i++)
{
Directions[i] = new MPQDCCDirection(new BitMuncher(data, directionOffsets[i] * 8), this);
}
}
}
}

View File

@ -21,6 +21,7 @@
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<NoWarn>IDE0049</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
@ -55,7 +56,12 @@
<Compile Include="Attributes\MessageFrameAttribute.cs" />
<Compile Include="Attributes\SceneAttribute.cs" />
<Compile Include="AutofacModule.cs" />
<Compile Include="Enums\eAnimationFrame.cs" />
<Compile Include="Enums\eArmorType.cs" />
<Compile Include="Enums\eCompositType.cs" />
<Compile Include="Enums\eDrawEffect.cs" />
<Compile Include="Enums\eMessageFrameType.cs" />
<Compile Include="Enums\eMobMode.cs" />
<Compile Include="Enums\eMovementType.cs" />
<Compile Include="Enums\ePanelFrameType.cs" />
<Compile Include="Enums\eButtonType.cs" />
@ -66,6 +72,7 @@
<Compile Include="Enums\eRenderCellType.cs" />
<Compile Include="Enums\eSessionType.cs" />
<Compile Include="Enums\eTextAlign.cs" />
<Compile Include="Enums\eWeaponClass.cs" />
<Compile Include="Enums\eWildBorder.cs" />
<Compile Include="Enums\Mobs\eDamageTypes.cs" />
<Compile Include="Enums\Mobs\eMobFlags.cs" />
@ -76,6 +83,10 @@
<Compile Include="Interfaces\MessageBus\IMessageFrame.cs" />
<Compile Include="Interfaces\MessageBus\ISessionManager.cs" />
<Compile Include="Interfaces\MessageBus\ISessionServer.cs" />
<Compile Include="Models\AnimationData.cs" />
<Compile Include="Models\BitMuncher.cs" />
<Compile Include="Models\MPQCOF.cs" />
<Compile Include="Models\MPQDCC.cs" />
<Compile Include="Models\PlayerInfo.cs" />
<Compile Include="Models\PlayerLocationDetails.cs" />
<Compile Include="Interfaces\UI\IButton.cs" />

View File

@ -125,6 +125,11 @@ namespace OpenDiablo2.Common
public static string LevelType = "data\\global\\excel\\LvlTypes.txt";
public static string LevelDetails = "data\\global\\excel\\Levels.txt";
// --- Animations ---
public static string ObjectData = "data\\global\\objects";
public static string AnimationData = "data\\global\\animdata.d2";
public static string PlayerAnimationBase = "data\\global\\CHARS";
}
}

View File

@ -11,6 +11,8 @@ namespace OpenDiablo2.Core
{
public sealed class EngineDataManager : IEngineDataManager
{
static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private readonly IMPQProvider mpqProvider;
public List<LevelPreset> LevelPresets { get; internal set; }
@ -28,6 +30,7 @@ namespace OpenDiablo2.Core
private void LoadLevelTypes()
{
log.Info("Loading level types");
var data = mpqProvider
.GetTextFile(ResourcePaths.LevelType)
.Skip(1)
@ -42,6 +45,7 @@ namespace OpenDiablo2.Core
private void LoadLevelPresets()
{
log.Info("Loading level presets");
var data = mpqProvider
.GetTextFile(ResourcePaths.LevelPreset)
.Skip(1)
@ -56,6 +60,7 @@ namespace OpenDiablo2.Core
private void LoadLevelDetails()
{
log.Info("Loading level details");
var data = mpqProvider
.GetTextFile(ResourcePaths.LevelDetails)
.Skip(1)

View File

@ -28,8 +28,7 @@ namespace OpenDiablo2.Core
private Dictionary<string, SoundEntry> soundTable = new Dictionary<string, SoundEntry>();
public Dictionary<string, Palette> PaletteTable { get; private set; } = new Dictionary<string, Palette>();
public GameEngine(
GlobalConfiguration globalConfig,
IMPQProvider mpqProvider,

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenDiablo2.Common;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Interfaces;
using OpenDiablo2.Common.Models;
@ -17,11 +19,16 @@ namespace OpenDiablo2.Core
private Dictionary<string, MPQFont> MPQFonts = new Dictionary<string, MPQFont>();
private Dictionary<string, Palette> Palettes = new Dictionary<string, Palette>();
private Dictionary<string, MPQDT1> DTs = new Dictionary<string, MPQDT1>();
private Dictionary<string, MPQCOF> PlayerCOFs = new Dictionary<string, MPQCOF>();
public Dictionary<string, List<AnimationData>> Animations { get; private set; } = new Dictionary<string, List<AnimationData>>();
public ResourceManager(IMPQProvider mpqProvider, IEngineDataManager engineDataManager)
{
this.mpqProvider = mpqProvider;
this.engineDataManager = engineDataManager;
Animations = AnimationData.LoadFromStream(mpqProvider.GetStream(ResourcePaths.AnimationData));
}
public ImageSet GetImageSet(string resourcePath)
@ -70,5 +77,30 @@ namespace OpenDiablo2.Core
return DTs[resourcePath];
}
public MPQCOF GetPlayerAnimation(eHero hero, eWeaponClass weaponClass, eMobMode mobMode)
{
var key = $"{hero.ToToken()}{mobMode.ToToken()}{weaponClass.ToToken()}";
if (PlayerCOFs.ContainsKey(key))
return PlayerCOFs[key];
var path = $"{ResourcePaths.PlayerAnimationBase}\\{hero.ToToken()}\\COF\\{hero.ToToken()}{mobMode.ToToken()}{weaponClass.ToToken()}.cof";
var result = MPQCOF.Load(mpqProvider.GetStream(path), Animations, hero, weaponClass, mobMode);
PlayerCOFs[key] = result;
return result;
}
public MPQDCC GetPlayerDCC(MPQCOF.COFLayer cofLayer, eArmorType armorType, Palette palette)
{
byte[] binaryData;
using (var stream = mpqProvider.GetStream(cofLayer.GetDCCPath(armorType)))
{
binaryData = new byte[stream.Length];
stream.Read(binaryData, 0, (int)stream.Length);
}
var result = new MPQDCC(binaryData, palette);
return result;
}
}
}

View File

@ -79,6 +79,10 @@ namespace OpenDiablo2.Scenes
var loadingSprite = renderWindow.LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading, new Point(300, 400));
// TODO: This is just a test
//var animation = resourceManager.GetPlayerAnimation(eHero.Necromancer, eWeaponClass.HandToHand, eMobMode.PlayerTownNeutral);
//var path = animation.Layers.First().GetDCCPath(eArmorType.Lite);
//var test = resourceManager.GetPlayerDCC(animation.Layers.First(), eArmorType.Lite, paletteProvider.PaletteTable["Units"]);
// Pre-load all the scenes for now until we fix the sdl load problem
var scenesToLoad = new string[] {"Select Hero Class" };

View File

@ -37,7 +37,6 @@ namespace OpenDiablo2.Scenes
private readonly IPaletteProvider paletteProvider;
private readonly IMPQProvider mpqProvider;
private readonly IMouseInfoProvider mouseInfoProvider;
private readonly IMusicProvider musicProvider;
private readonly ISceneManager sceneManager;
private readonly ITextDictionary textDictionary;
private readonly IKeyboardInfoProvider keyboardInfoProvider;
@ -58,7 +57,6 @@ namespace OpenDiablo2.Scenes
IPaletteProvider paletteProvider,
IMPQProvider mpqProvider,
IMouseInfoProvider mouseInfoProvider,
IMusicProvider musicProvider,
ISceneManager sceneManager,
Func<eButtonType, IButton> createButton,
Func<ITextBox> createTextBox,

View File

@ -24,7 +24,6 @@ namespace OpenDiablo2.ServiceBus
private RequestSocket requestSocket;
private AutoResetEvent resetEvent = new AutoResetEvent(false);
private ISessionServer sessionServer;
public Guid PlayerId { get; private set; }
private bool running = false;
public OnSetSeedEvent OnSetSeed { get; set; }