diff --git a/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs b/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs new file mode 100644 index 00000000..9c9f2c5d --- /dev/null +++ b/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenDiablo2.Common.Models; + +namespace OpenDiablo2.Common.Interfaces +{ + public interface IEngineDataManager + { + List LevelPresets { get; } + List LevelTypes { get; } + } +} diff --git a/OpenDiablo2.Common/Interfaces/IResourceManager.cs b/OpenDiablo2.Common/Interfaces/IResourceManager.cs index 0cc15dcb..5343f34e 100644 --- a/OpenDiablo2.Common/Interfaces/IResourceManager.cs +++ b/OpenDiablo2.Common/Interfaces/IResourceManager.cs @@ -11,6 +11,8 @@ namespace OpenDiablo2.Common.Interfaces { ImageSet GetImageSet(string resourcePath); MPQFont GetMPQFont(string resourcePath); + MPQDS1 GetMPQDS1(string resourcePath, int definition, int act); + MPQDT1 GetMPQDT1(string resourcePath); Palette GetPalette(string paletteName); } } diff --git a/OpenDiablo2.Common/Models/LevelPreset.cs b/OpenDiablo2.Common/Models/LevelPreset.cs new file mode 100644 index 00000000..113ed873 --- /dev/null +++ b/OpenDiablo2.Common/Models/LevelPreset.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenDiablo2.Common.Models +{ + // + public sealed class LevelPreset + { + public string Name { get; set; } + public int Def { get; set; } + public int LevelId { get; set; } + public bool Populate { get; set; } + public int Logicals { get; set; } + public int Outdoors { get; set; } + public int Animate { get; set; } + public int KillEdge { get; set; } + public int FillBlanks { get; set; } + public int SizeX { get; set; } + public int SizeY { get; set; } + public int AutoMap { get; set; } + public int Scan { get; set; } + public int Pops { get; set; } + public int PopPad { get; set; } + public int Files { get; set; } + public string File1 { get; set; } + public string File2 { get; set; } + public string File3 { get; set; } + public string File4 { get; set; } + public string File5 { get; set; } + public string File6 { get; set; } + public UInt32 Dt1Mask { get; set; } + public bool Beta { get; set; } + } + + public static class LevelPresetHelper + { + public static LevelPreset ToLevelPreset(this string[] row) + => new LevelPreset + { + Name = row[0], + Def = Convert.ToInt32(row[1]), + LevelId = row[2] == "#REF!" ? 0 : Convert.ToInt32(row[2]), // TODO: Why is this a thing? + Populate = Convert.ToInt32(row[3]) == 1, + Logicals = Convert.ToInt32(row[4]), + Outdoors = Convert.ToInt32(row[5]), + Animate = Convert.ToInt32(row[6]), + KillEdge = Convert.ToInt32(row[7]), + FillBlanks = Convert.ToInt32(row[8]), + SizeX = Convert.ToInt32(row[9]), + SizeY = Convert.ToInt32(row[10]), + AutoMap = Convert.ToInt32(row[11]), + Scan = Convert.ToInt32(row[12]), + Pops = Convert.ToInt32(row[13]), + PopPad = Convert.ToInt32(row[14]), + Files = Convert.ToInt32(row[15]), + File1 = row[16], + File2 = row[17], + File3 = row[18], + File4 = row[19], + File5 = row[20], + File6 = row[21], + Dt1Mask = Convert.ToUInt32(row[22]), + Beta = Convert.ToInt32(row[23]) == 1, + }; + } +} diff --git a/OpenDiablo2.Common/Models/LevelType.cs b/OpenDiablo2.Common/Models/LevelType.cs new file mode 100644 index 00000000..3a7871be --- /dev/null +++ b/OpenDiablo2.Common/Models/LevelType.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenDiablo2.Common.Models +{ + public sealed class LevelType + { + public string Name { get; set; } + public int Id { get; set; } + public string[] File { get; set; } = new string[32]; + public bool Beta { get; set; } + public int Act { get; set; } + } + + public static class LevelTypeHelper + { + public static LevelType ToLevelType(this string[] row) + { + var result = new LevelType + { + Name = row[0], + Id = Convert.ToInt32(row[1]), + Beta = Convert.ToInt32(row[34]) == 1, + Act = Convert.ToInt32(row[35]) + }; + + + for (int i = 0; i < 32; i++) + { + result.File[i] = row[i + 2]; + } + + return result; + } + } +} diff --git a/OpenDiablo2.Common/Models/MPQDS1.cs b/OpenDiablo2.Common/Models/MPQDS1.cs new file mode 100644 index 00000000..0f2777d0 --- /dev/null +++ b/OpenDiablo2.Common/Models/MPQDS1.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenDiablo2.Common.Interfaces; + +namespace OpenDiablo2.Common.Models +{ + + public sealed class MPQDS1TileProps + { + public byte Prop1 { get; internal set; } + public byte Prop2 { get; internal set; } + public byte Prop3 { get; internal set; } + public byte Prop4 { get; internal set; } + } + + public sealed class MPQDS1WallOrientationTileProps + { + public byte Orientation1 { get; internal set; } + public byte Orientation2 { get; internal set; } + public byte Orientation3 { get; internal set; } + public byte Orientation4 { get; internal set; } + } + + public sealed class MPQDS1WallLayer + { + public MPQDS1TileProps[] Props { get; internal set; } + public MPQDS1WallOrientationTileProps[] Orientations { get; internal set; } + } + + public sealed class MPQDS1FloorLayer + { + public MPQDS1TileProps[] Props { get; internal set; } + } + + public sealed class MPQDS1ShadowLayer + { + public MPQDS1TileProps[] Props { get; internal set; } + } + + public sealed class MPQDS1Object + { + public UInt32 Type { get; internal set; } + public UInt32 Id { get; internal set; } + public UInt32 X { get; internal set; } + public UInt32 Y { get; internal set; } + public UInt32 DS1Flags { get; internal set; } + } + + public sealed class MPQDS1 + { + static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + public UInt32 Version { get; internal set; } + public UInt32 Width { get; internal set; } + public UInt32 Height { get; internal set; } + public UInt32 Act { get; internal set; } // ??? + public UInt32 TagType { get; internal set; } // ??? + public UInt32 FileCount { get; internal set; } + public UInt32 NumberOfWalls { get; internal set; } + public UInt32 NumberOfFloors { get; internal set; } + public UInt32 NumberOfObjects { get; internal set; } + public UInt32 NumberOfNPCs { get; internal set; } + + public MPQDT1[] DT1s = new MPQDT1[32]; + + public List FileNames { get; internal set; } = new List(); + public List WallLayers { get; internal set; } = new List(); + public List FloorLayers { get; internal set; } = new List(); + public MPQDS1ShadowLayer ShadowLayer { get; internal set; } = new MPQDS1ShadowLayer(); + public List Objects { get; internal set; } = new List(); + + // TODO: DI magic please + public MPQDS1(Stream stream, string fileName, int definition, int act, IEngineDataManager engineDataManager, IResourceManager resourceManager) + { + log.Debug($"Loading {fileName} (Act {act}) Def {definition}"); + var br = new BinaryReader(stream); + Version = br.ReadUInt32(); + Width = br.ReadUInt32() + 1; + Height = br.ReadUInt32() + 1; + Act = br.ReadUInt32(); + TagType = br.ReadUInt32(); + FileCount = br.ReadUInt32(); + + + + if (TagType != 0) + throw new ApplicationException("We don't currently handle those tag things..."); + + for (int i = 0; i < FileCount; i++) + { + var fn = ""; + while (true) + { + var b = br.ReadByte(); + if (b == 0) + break; + fn += (char)b; + } + if (fn.StartsWith("\\d2\\")) + fn = fn.Substring(4); + FileNames.Add(fn); + } + + NumberOfWalls = br.ReadUInt32(); + NumberOfFloors = br.ReadUInt32(); + + for (int i = 0; i < NumberOfWalls; i++) + { + var wallLayer = new MPQDS1WallLayer + { + Props = new MPQDS1TileProps[Width * Height], + Orientations = new MPQDS1WallOrientationTileProps[Width * Height] + }; + + for (int y = 0; y < Height; y++) + { + for (int x = 0; x < Width; x++) + { + wallLayer.Props[x + (y * Width)] = new MPQDS1TileProps + { + Prop1 = br.ReadByte(), + Prop2 = br.ReadByte(), + Prop3 = br.ReadByte(), + Prop4 = br.ReadByte() + }; + } + } + + for (int y = 0; y < Height; y++) + { + for (int x = 0; x < Width; x++) + { + wallLayer.Orientations[x + (y * Width)] = new MPQDS1WallOrientationTileProps + { + Orientation1 = br.ReadByte(), + Orientation2 = br.ReadByte(), + Orientation3 = br.ReadByte(), + Orientation4 = br.ReadByte() + }; + } + } + WallLayers.Add(wallLayer); + } + + for (int i = 0; i < NumberOfFloors; i++) + { + var floorLayer = new MPQDS1FloorLayer + { + Props = new MPQDS1TileProps[Width * Height] + }; + + for (int y = 0; y < Height; y++) + { + for (int x = 0; x < Width; x++) + { + floorLayer.Props[x + (y * Width)] = new MPQDS1TileProps + { + Prop1 = br.ReadByte(), + Prop2 = br.ReadByte(), + Prop3 = br.ReadByte(), + Prop4 = br.ReadByte() + }; + } + } + FloorLayers.Add(floorLayer); + } + + ShadowLayer.Props = new MPQDS1TileProps[Width * Height]; + for (int y = 0; y < Height; y++) + { + for (int x = 0; x < Width; x++) + { + ShadowLayer.Props[x + (y * Width)] = new MPQDS1TileProps + { + Prop1 = br.ReadByte(), + Prop2 = br.ReadByte(), + Prop3 = br.ReadByte(), + Prop4 = br.ReadByte() + }; + } + } + + // TODO: Tag layer goes here (tag = 1) + + NumberOfObjects = br.ReadUInt32(); + for (int i = 0; i < NumberOfObjects; i++) + { + Objects.Add(new MPQDS1Object + { + Type = br.ReadUInt32(), + Id = br.ReadUInt32(), + X = br.ReadUInt32(), + Y = br.ReadUInt32(), + DS1Flags = br.ReadUInt32() + }); + } + + // TODO: Option groups go here (tag = 1) + + NumberOfNPCs = br.ReadUInt32(); + + + // TODO: WalkPaths + + LevelPreset levelPreset; + if (definition == -1) + { + levelPreset = engineDataManager.LevelPresets.First(x => + x.File1.ToLower() == fileName.ToLower() + || x.File2.ToLower() == fileName.ToLower() + || x.File3.ToLower() == fileName.ToLower() + || x.File4.ToLower() == fileName.ToLower() + || x.File5.ToLower() == fileName.ToLower() + || x.File6.ToLower() == fileName.ToLower()); + } + else + { + levelPreset = engineDataManager.LevelPresets.First(x => x.Def == definition); + } + + var dt1Mask = levelPreset.Dt1Mask; + var levelType = engineDataManager.LevelTypes.First(x => x.Id == levelPreset.LevelId && x.Act == act); + + for (int i = 0; i < 32; i++) + { + var tilePath = levelType.File[i]; + var isMasked = ((dt1Mask >> i) & 1) == 1; + if (!isMasked) + continue; + + log.Debug($"Loading DT resource {levelType.File[i]}"); + + DT1s[i] = resourceManager.GetMPQDT1("data\\global\\tiles\\" + levelType.File[i].Replace("/", "\\")); + } + } + } +} diff --git a/OpenDiablo2.Common/Models/MPQDT1.cs b/OpenDiablo2.Common/Models/MPQDT1.cs new file mode 100644 index 00000000..a7cdb634 --- /dev/null +++ b/OpenDiablo2.Common/Models/MPQDT1.cs @@ -0,0 +1,179 @@ +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 MPQDT1Block + { + public UInt16 PositionX { get; internal set; } + public UInt16 PositionY { get; internal set; } + public byte GridX { get; internal set; } + public byte GridY { get; internal set; } + public UInt16 Format { get; internal set; } + public UInt32 Length { get; internal set; } + public Int32 FileOffset { get; internal set; } + public Int16[] PixelData { get; internal set; } + } + + public sealed class MPQDT1Tile + { + public UInt32 Direction { get; internal set; } + public UInt16 RoofHeight { get; internal set; } + public byte SoundIndex { get; internal set; } + public bool Animated { get; internal set; } + public int Height { get; internal set; } + public int Width { get; internal set; } + public UInt32 Orientation { get; internal set; } + public UInt32 MainIndex { get; internal set; } + public UInt32 SubIndex { get; internal set; } + public UInt32 RarityOrFrameIndex { get; internal set; }// Frame index if animated floor tile + public byte[] SubTileFlags { get; internal set; } = new byte[25]; + public UInt32 BlockHeadersPointer { get; internal set; } // Pointer to block headers for this tile + public UInt32 BlockDataLength { get; internal set; } // Block headers + block data of this tile + public UInt32 NumberOfBlocks { get; internal set; } + public MPQDT1Block[] Blocks { get; internal set; } + } + + public sealed class MPQDT1 + { + public UInt32 NumberOfTiles { get; private set; } + public MPQDT1Tile[] Tiles { get; private set; } + + public MPQDT1(Stream stream) + { + var br = new BinaryReader(stream); + + stream.Seek(268, SeekOrigin.Begin); // Skip useless header info + NumberOfTiles = br.ReadUInt32(); + var tileHeaderOffset = br.ReadUInt32(); + stream.Seek(tileHeaderOffset, SeekOrigin.Begin); + Tiles = new MPQDT1Tile[NumberOfTiles]; + + for (int tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++) + { + Tiles[tileIndex] = new MPQDT1Tile(); + Tiles[tileIndex].Direction = br.ReadUInt32(); + Tiles[tileIndex].RoofHeight = br.ReadUInt16(); + Tiles[tileIndex].SoundIndex = br.ReadByte(); + Tiles[tileIndex].Animated = br.ReadByte() == 1; + Tiles[tileIndex].Height = br.ReadInt32(); + Tiles[tileIndex].Width = br.ReadInt32(); + br.ReadBytes(4); + Tiles[tileIndex].Orientation = br.ReadUInt32(); + Tiles[tileIndex].MainIndex = br.ReadUInt32(); + Tiles[tileIndex].SubIndex = br.ReadUInt32(); + Tiles[tileIndex].RarityOrFrameIndex = br.ReadUInt32(); + br.ReadBytes(4); + for (int i = 0; i < 25; i++) + Tiles[tileIndex].SubTileFlags[i] = br.ReadByte(); + br.ReadBytes(7); + Tiles[tileIndex].BlockHeadersPointer = br.ReadUInt32(); + Tiles[tileIndex].BlockDataLength = br.ReadUInt32(); + Tiles[tileIndex].NumberOfBlocks = br.ReadUInt32(); + br.ReadBytes(12); + } + + + for (int tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++) + { + var tile = Tiles[tileIndex]; + if (tile.BlockHeadersPointer == 0) + { + tile.Blocks = new MPQDT1Block[0]; + continue; + } + stream.Seek(tile.BlockHeadersPointer, SeekOrigin.Begin); + + tile.Blocks = new MPQDT1Block[tile.NumberOfBlocks]; + for (int blockIndex = 0; blockIndex < tile.NumberOfBlocks; blockIndex++) + { + tile.Blocks[blockIndex] = new MPQDT1Block(); + tile.Blocks[blockIndex].PositionX = br.ReadUInt16(); + tile.Blocks[blockIndex].PositionY = br.ReadUInt16(); + br.ReadBytes(2); + tile.Blocks[blockIndex].GridX = br.ReadByte(); + tile.Blocks[blockIndex].GridX = br.ReadByte(); + tile.Blocks[blockIndex].Format = br.ReadUInt16(); + tile.Blocks[blockIndex].Length = br.ReadUInt32(); + br.ReadBytes(2); + tile.Blocks[blockIndex].FileOffset = br.ReadInt32(); + } + + for (int blockIndex = 0; blockIndex < tile.NumberOfBlocks; blockIndex++) + { + var block = tile.Blocks[blockIndex]; + stream.Seek(tile.BlockHeadersPointer + block.FileOffset, SeekOrigin.Begin); + + if (block.Format == 1) + { + // 3D isometric block + if (block.Length != 256) + throw new ApplicationException($"Expected exactly 256 bytes of data, but got {block.Length} instead!"); + + int x = 0; + int y = 0; + int n = 0; + int[] xjump = { 14, 12, 10, 8, 6, 4, 2, 0, 2, 4, 6, 8, 10, 12, 14 }; + int[] nbpix = { 4, 8, 12, 16, 20, 24, 28, 32, 28, 24, 20, 16, 12, 8, 4 }; + var length = 256; + block.PixelData = new Int16[32 * 16]; + while (length > 0) + { + x = xjump[y]; + n = nbpix[y]; + length -= n; + while (n > 0) + { + block.PixelData[x + (y * 32)] = br.ReadByte(); + x++; + n--; + } + y++; + } + + } + else + { + // TODO: This doesn't work.. memory pointer issues? + continue; + + // RLE block + var length = block.Length; + byte b1; + byte b2; + int x = 0; + int y = 0; + block.PixelData = new Int16[32 * 16]; + while (length > 0) + { + b1 = br.ReadByte(); + b2 = br.ReadByte(); + length -= 2; + if (b1 != 0 || b2 != 0) + { + x += b1; + length -= b2; + while(b2 > 0) + { + block.PixelData[x + (y * 32)] = br.ReadByte(); + br.ReadByte(); + x++; + b2--; + } + } + else + { + x = 0; + y++; + } + } + } + } + } + } + } +} diff --git a/OpenDiablo2.Common/OpenDiablo2.Common.csproj b/OpenDiablo2.Common/OpenDiablo2.Common.csproj index e875c624..c2db1c4d 100644 --- a/OpenDiablo2.Common/OpenDiablo2.Common.csproj +++ b/OpenDiablo2.Common/OpenDiablo2.Common.csproj @@ -73,6 +73,7 @@ + @@ -91,6 +92,10 @@ + + + + diff --git a/OpenDiablo2.Common/ResourcePaths.cs b/OpenDiablo2.Common/ResourcePaths.cs index 0df40e58..e2b90901 100644 --- a/OpenDiablo2.Common/ResourcePaths.cs +++ b/OpenDiablo2.Common/ResourcePaths.cs @@ -106,6 +106,11 @@ namespace OpenDiablo2.Common // TODO: Doesn't sound right :) public static string EnglishTable = "data\\local\\lng\\eng\\English.txt"; public static string ExpansionStringTable = "data\\local\\lng\\eng\\expansionstring.tbl"; + public static string LevelPreset = "data\\global\\excel\\LvlPrest.txt"; + public static string LevelType = "data\\global\\excel\\LvlTypes.txt"; + + // --- Maps --- + public static string MapAct1TownE1 = "data\\global\\tiles\\ACT1\\Town\\townE1.ds1"; } } diff --git a/OpenDiablo2.Core/AutofacModule.cs b/OpenDiablo2.Core/AutofacModule.cs index 141de6b7..e797cb40 100644 --- a/OpenDiablo2.Core/AutofacModule.cs +++ b/OpenDiablo2.Core/AutofacModule.cs @@ -22,9 +22,10 @@ namespace OpenDiablo2.Core builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType