diff --git a/OpenDiablo2.Common/Interfaces/IMPQProvider.cs b/OpenDiablo2.Common/Interfaces/IMPQProvider.cs index 2f3210fc..16f4ab93 100644 --- a/OpenDiablo2.Common/Interfaces/IMPQProvider.cs +++ b/OpenDiablo2.Common/Interfaces/IMPQProvider.cs @@ -8,7 +8,7 @@ namespace OpenDiablo2.Common.Interfaces public interface IMPQProvider { IEnumerable GetMPQs(); - IEnumerable> GetTextFile(string fileName); + IEnumerable GetTextFile(string fileName); Stream GetStream(string fileName); } } diff --git a/OpenDiablo2.Common/Models/LevelPreset.cs b/OpenDiablo2.Common/Models/LevelPreset.cs index 113ed873..b79f1641 100644 --- a/OpenDiablo2.Common/Models/LevelPreset.cs +++ b/OpenDiablo2.Common/Models/LevelPreset.cs @@ -42,7 +42,7 @@ namespace OpenDiablo2.Common.Models { Name = row[0], Def = Convert.ToInt32(row[1]), - LevelId = row[2] == "#REF!" ? 0 : Convert.ToInt32(row[2]), // TODO: Why is this a thing? + LevelId = Convert.ToInt32(row[2]), Populate = Convert.ToInt32(row[3]) == 1, Logicals = Convert.ToInt32(row[4]), Outdoors = Convert.ToInt32(row[5]), diff --git a/OpenDiablo2.Common/Models/LevelType.cs b/OpenDiablo2.Common/Models/LevelType.cs index 3a7871be..a93ea45a 100644 --- a/OpenDiablo2.Common/Models/LevelType.cs +++ b/OpenDiablo2.Common/Models/LevelType.cs @@ -10,7 +10,7 @@ namespace OpenDiablo2.Common.Models { public string Name { get; set; } public int Id { get; set; } - public string[] File { get; set; } = new string[32]; + public string[] File { get; set; } = new string[33]; public bool Beta { get; set; } public int Act { get; set; } } diff --git a/OpenDiablo2.Common/Models/MPQDS1.cs b/OpenDiablo2.Common/Models/MPQDS1.cs index c9fbf80e..338879a5 100644 --- a/OpenDiablo2.Common/Models/MPQDS1.cs +++ b/OpenDiablo2.Common/Models/MPQDS1.cs @@ -50,6 +50,14 @@ namespace OpenDiablo2.Common.Models public Int32 DS1Flags { get; internal set; } } + public sealed class MPQDS1Group + { + public Int32 TileX { get; internal set; } + public Int32 TileY { get; internal set; } + public Int32 Width { get; internal set; } + public Int32 Height { get; internal set; } + } + public sealed class MPQDS1 { static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); @@ -64,14 +72,17 @@ namespace OpenDiablo2.Common.Models public Int32 NumberOfFloors { get; internal set; } public Int32 NumberOfObjects { get; internal set; } public Int32 NumberOfNPCs { get; internal set; } + public Int32 NumberOfTags { get; internal set; } + public Int32 NumberOfGroups { get; internal set; } + + public MPQDT1[] DT1s = new MPQDT1[33]; - 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(); + public MPQDS1WallLayer[] WallLayers { get; internal set; } + public MPQDS1FloorLayer[] FloorLayers { get; internal set; } + public MPQDS1ShadowLayer[] ShadowLayers { get; internal set; } + public MPQDS1Object[] Objects { get; internal set; } + public MPQDS1Group[] Groups { get; internal set; } // TODO: DI magic please public MPQDS1(Stream stream, string fileName, int definition, int act, IEngineDataManager engineDataManager, IResourceManager resourceManager) @@ -81,130 +92,232 @@ namespace OpenDiablo2.Common.Models Version = br.ReadInt32(); Width = br.ReadInt32() + 1; Height = br.ReadInt32() + 1; - Act = br.ReadInt32(); - TagType = br.ReadInt32(); - FileCount = br.ReadInt32(); + Act = br.ReadInt32() + 1; - - - if (TagType != 0) - throw new ApplicationException("We don't currently handle those tag things..."); - - for (int i = 0; i < FileCount; i++) + if (Version >= 10) { - 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); + TagType = br.ReadInt32(); + if (TagType == 1 || TagType == 2) + NumberOfTags = 1; } - NumberOfWalls = br.ReadInt32(); - NumberOfFloors = br.ReadInt32(); - - for (int i = 0; i < NumberOfWalls; i++) + FileCount = 0; + if (Version >= 3) { - var wallLayer = new MPQDS1WallLayer + FileCount = br.ReadInt32(); + for (int i = 0; i < FileCount; i++) { - Props = new MPQDS1TileProps[Width * Height], - Orientations = new MPQDS1WallOrientationTileProps[Width * Height] + 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); + } + } + + if (Version >= 9 && Version <= 13) + { + br.ReadBytes(8); + } + + if (Version >= 4) + { + NumberOfWalls = br.ReadInt32(); + + if (Version >= 16) + { + NumberOfFloors = br.ReadInt32(); + } + else NumberOfFloors = 1; + } + else + { + NumberOfFloors = 1; + NumberOfWalls = 1; + NumberOfTags = 1; + } + + + + var layoutStream = new List(); + + if (Version < 4) + { + layoutStream.AddRange(new int[] { 1, 9, 5, 12, 11 }); + } + else + { + for (var x = 0; x < NumberOfWalls; x++) + { + layoutStream.Add(1 + x); + layoutStream.Add(5 + x); + } + for (var x = 0; x < NumberOfFloors; x++) + layoutStream.Add(9 + x); + + layoutStream.Add(11); + + if (NumberOfTags > 0) + layoutStream.Add(12); + } + + WallLayers = new MPQDS1WallLayer[NumberOfWalls]; + for (var l = 0; l < NumberOfWalls; l++) + { + WallLayers[l] = new MPQDS1WallLayer + { + Orientations = new MPQDS1WallOrientationTileProps[Width * Height], + Props = new MPQDS1TileProps[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++) + FloorLayers = new MPQDS1FloorLayer[NumberOfFloors]; + for (var l = 0; l < NumberOfFloors; l++) { - var floorLayer = new MPQDS1FloorLayer + FloorLayers[l] = new MPQDS1FloorLayer { Props = new MPQDS1TileProps[Width * Height] }; + } - for (int y = 0; y < Height; y++) + ShadowLayers = new MPQDS1ShadowLayer[1]; + for (var l = 0; l < 1; l++) + { + ShadowLayers[l] = new MPQDS1ShadowLayer { - for (int x = 0; x < Width; x++) + Props = new MPQDS1TileProps[Width * Height] + }; + } + + + for (int n = 0; n < layoutStream.Count(); n++) + { + for (var y = 0; y < Height; y++) + { + for (var x = 0; x < Width; x++) { - floorLayer.Props[x + (y * Width)] = new MPQDS1TileProps + switch (layoutStream[n]) { - Prop1 = br.ReadByte(), - Prop2 = br.ReadByte(), - Prop3 = br.ReadByte(), - Prop4 = br.ReadByte() - }; + // Walls + case 1: + case 2: + case 3: + case 4: + WallLayers[layoutStream[n] - 1].Props[x + (y * Width)] = new MPQDS1TileProps + { + Prop1 = br.ReadByte(), + Prop2 = br.ReadByte(), + Prop3 = br.ReadByte(), + Prop4 = br.ReadByte() + }; + break; + + // Orientations + case 5: + case 6: + case 7: + case 8: + // TODO: Orientations + if (Version < 7) + { + br.ReadBytes(4); + } + else + { + br.ReadBytes(4); + } + break; + + // Floors + case 9: + case 10: + FloorLayers[layoutStream[n] - 9].Props[x + (y * Width)] = new MPQDS1TileProps + { + Prop1 = br.ReadByte(), + Prop2 = br.ReadByte(), + Prop3 = br.ReadByte(), + Prop4 = br.ReadByte() + }; + break; + // Shadow + case 11: + ShadowLayers[layoutStream[n] - 11].Props[x + (y * Width)] = new MPQDS1TileProps + { + Prop1 = br.ReadByte(), + Prop2 = br.ReadByte(), + Prop3 = br.ReadByte(), + Prop4 = br.ReadByte() + }; + break; + // Tags + case 12: + // TODO: Tags + br.ReadBytes(4); + break; + } } } - FloorLayers.Add(floorLayer); } - - ShadowLayer.Props = new MPQDS1TileProps[Width * Height]; - for (int y = 0; y < Height; y++) + /* + NumberOfObjects = 0; + if (Version >= 2) { - for (int x = 0; x < Width; x++) + + NumberOfObjects = br.ReadInt32(); + Objects = new MPQDS1Object[NumberOfObjects]; + + for (int i = 0; i < NumberOfObjects; i++) { - ShadowLayer.Props[x + (y * Width)] = new MPQDS1TileProps + Objects[i] = new MPQDS1Object { - Prop1 = br.ReadByte(), - Prop2 = br.ReadByte(), - Prop3 = br.ReadByte(), - Prop4 = br.ReadByte() + Type = br.ReadInt32(), + Id = br.ReadInt32(), + X = br.ReadInt32(), + Y = br.ReadInt32() }; + + if (Version > 5) + Objects[i].DS1Flags = br.ReadInt32(); + } } - // TODO: Tag layer goes here (tag = 1) - NumberOfObjects = br.ReadInt32(); - for (int i = 0; i < NumberOfObjects; i++) + if (Version >= 12 && TagType == 1 || TagType == 2) { - Objects.Add(new MPQDS1Object + if (Version >= 18) + br.ReadBytes(4); + + NumberOfGroups = br.ReadInt32(); + } + else NumberOfGroups = 0; + + + Groups = new MPQDS1Group[NumberOfGroups]; + for (var x = 0; x < NumberOfGroups; x++) + { + Groups[x] = new MPQDS1Group { - Type = br.ReadInt32(), - Id = br.ReadInt32(), - X = br.ReadInt32(), - Y = br.ReadInt32(), - DS1Flags = br.ReadInt32() - }); + TileX = br.ReadInt32(), + TileY = br.ReadInt32(), + Width = br.ReadInt32(), + Height = br.ReadInt32(), + }; + if (Version >= 13) + br.ReadBytes(4); } - // TODO: Option groups go here (tag = 1) + if (Version >= 14) + { - NumberOfNPCs = br.ReadInt32(); - - - // TODO: WalkPaths + NumberOfNPCs = br.ReadInt32(); + }*/ LevelPreset levelPreset; if (definition == -1) @@ -225,6 +338,7 @@ namespace OpenDiablo2.Common.Models 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]; diff --git a/OpenDiablo2.Common/Models/MPQDT1.cs b/OpenDiablo2.Common/Models/MPQDT1.cs index 2ea3e460..efe5092b 100644 --- a/OpenDiablo2.Common/Models/MPQDT1.cs +++ b/OpenDiablo2.Common/Models/MPQDT1.cs @@ -135,7 +135,7 @@ namespace OpenDiablo2.Common.Models 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]; + block.PixelData = new Int16[32 * 32]; while (length > 0) { x = xjump[y]; @@ -143,7 +143,7 @@ namespace OpenDiablo2.Common.Models length -= n; while (n > 0) { - block.PixelData[x + (y * 32)] = br.ReadByte(); + block.PixelData[x + ((31-y) * 32)] = br.ReadByte(); x++; n--; } @@ -176,7 +176,7 @@ namespace OpenDiablo2.Common.Models length -= b2; while (b2 > 0) { - block.PixelData[x + (y * 32)] = br.ReadByte(); + block.PixelData[x + ((31-y) * 32)] = br.ReadByte(); x++; b2--; } diff --git a/OpenDiablo2.Core/EngineDataManager.cs b/OpenDiablo2.Core/EngineDataManager.cs index 042b4ac7..3340699e 100644 --- a/OpenDiablo2.Core/EngineDataManager.cs +++ b/OpenDiablo2.Core/EngineDataManager.cs @@ -28,10 +28,10 @@ namespace OpenDiablo2.Core { var data = mpqProvider .GetTextFile(ResourcePaths.LevelType) - .First() .Skip(1) .Where(x => !String.IsNullOrWhiteSpace(x)) .Select(x => x.Split('\t')) + .Where(x => x.Count() == 36 && x[0] != "Expansion") .ToArray() .Select(x => x.ToLevelType()); @@ -42,10 +42,10 @@ namespace OpenDiablo2.Core { var data = mpqProvider .GetTextFile(ResourcePaths.LevelPreset) - .First() .Skip(1) .Where(x => !String.IsNullOrWhiteSpace(x)) .Select(x => x.Split('\t')) + .Where(x => x.Count() == 24 && x[0] != "Expansion") .ToArray() .Select(x => x.ToLevelPreset()); diff --git a/OpenDiablo2.Core/GameEngine.cs b/OpenDiablo2.Core/GameEngine.cs index 73565e8e..381168e9 100644 --- a/OpenDiablo2.Core/GameEngine.cs +++ b/OpenDiablo2.Core/GameEngine.cs @@ -65,14 +65,14 @@ namespace OpenDiablo2.Core private void LoadSoundData() { log.Info("Loading sound configuration data"); - foreach (var soundDescFile in mpqProvider.GetTextFile("data\\global\\excel\\Sounds.txt")) + var soundDescFile = mpqProvider.GetTextFile("data\\global\\excel\\Sounds.txt"); + + foreach (var row in soundDescFile.Skip(1).Where(x => !String.IsNullOrWhiteSpace(x))) { - foreach (var row in soundDescFile.Skip(1).Where(x => !String.IsNullOrWhiteSpace(x))) - { - var soundEntry = row.ToSoundEntry(); - soundTable[soundEntry.Handle] = soundEntry; - } + var soundEntry = row.ToSoundEntry(); + soundTable[soundEntry.Handle] = soundEntry; } + } public void Run() diff --git a/OpenDiablo2.Core/MPQProvider.cs b/OpenDiablo2.Core/MPQProvider.cs index f4397ab9..6a191d05 100644 --- a/OpenDiablo2.Core/MPQProvider.cs +++ b/OpenDiablo2.Core/MPQProvider.cs @@ -18,33 +18,52 @@ namespace OpenDiablo2.Core public MPQProvider(GlobalConfiguration globalConfiguration) { this.globalConfiguration = globalConfiguration; + // TODO: Make this less dumb. We need to an external file to configure mpq load order. + var mpqsToLoad = new string[] + { + + }; this.mpqs = Directory .EnumerateFiles(globalConfiguration.BaseDataPath, "*.mpq") .Where(x => !Path.GetFileName(x).StartsWith("patch")) .Select(file => new MPQ(file)) .ToArray(); + + + // Load the base game files for(var i = 0; i < mpqs.Count(); i++) { + if (Path.GetFileName(mpqs[i].Path).StartsWith("d2exp") || Path.GetFileName(mpqs[i].Path).StartsWith("d2x")) + continue; + foreach(var file in mpqs[i].Files) { mpqLookup[file.ToLower()] = i; } } + + // Load the expansion game files + for (var i = 0; i < mpqs.Count(); i++) + { + if (!Path.GetFileName(mpqs[i].Path).StartsWith("d2exp") && !Path.GetFileName(mpqs[i].Path).StartsWith("d2x")) + continue; + + foreach (var file in mpqs[i].Files) + { + mpqLookup[file.ToLower()] = i; + } + } } public IEnumerable GetMPQs() => mpqs; public Stream GetStream(string fileName) - { - return mpqs[mpqLookup[fileName.ToLower()]].OpenFile(fileName); - } + => mpqs[mpqLookup[fileName.ToLower()]].OpenFile(fileName); + - public IEnumerable> GetTextFile(string fileName) - { - foreach (var stream in mpqs.Where(x => x.Files.Contains(fileName)).Select(x => x.OpenFile(fileName))) - yield return new StreamReader(stream).ReadToEnd().Split('\n').Select(x => x.Trim()); - } + public IEnumerable GetTextFile(string fileName) + => new StreamReader(mpqs[mpqLookup[fileName.ToLower()]].OpenFile(fileName)).ReadToEnd().Split('\n'); } diff --git a/OpenDiablo2.Core/TextDictionary.cs b/OpenDiablo2.Core/TextDictionary.cs index c8bbbc54..d00d10a6 100644 --- a/OpenDiablo2.Core/TextDictionary.cs +++ b/OpenDiablo2.Core/TextDictionary.cs @@ -89,7 +89,7 @@ namespace OpenDiablo2.Core private void LoadDictionary() { - var text = mpqProvider.GetTextFile(ResourcePaths.EnglishTable).First(); + var text = mpqProvider.GetTextFile(ResourcePaths.EnglishTable); var rowstoLoad = text.Where(x => x.Split(',').Count() == 3).Select(x => x.Split(',').Select(z => z.Trim()).ToArray()); foreach (var row in rowstoLoad) diff --git a/OpenDiablo2.SDL2/SDL2RenderWindow.cs b/OpenDiablo2.SDL2/SDL2RenderWindow.cs index d4d4eacd..8c99b48f 100644 --- a/OpenDiablo2.SDL2/SDL2RenderWindow.cs +++ b/OpenDiablo2.SDL2/SDL2RenderWindow.cs @@ -170,6 +170,9 @@ namespace OpenDiablo2.SDL2_ public void Draw(ISprite sprite) { var spr = sprite as SDL2Sprite; + if (spr.texture == IntPtr.Zero) + return; + var loc = spr.GetRenderPoint(); var destRect = new SDL.SDL_Rect diff --git a/OpenDiablo2.SDL2/SDL2Sprite.cs b/OpenDiablo2.SDL2/SDL2Sprite.cs index 9b555d16..a7d0d4d3 100644 --- a/OpenDiablo2.SDL2/SDL2Sprite.cs +++ b/OpenDiablo2.SDL2/SDL2Sprite.cs @@ -16,7 +16,6 @@ namespace OpenDiablo2.SDL2_ internal readonly ImageSet source; private readonly IntPtr renderer; internal IntPtr texture = IntPtr.Zero; - private Point globalFrameOffset = new Point(0, 0); public Point Location { get; set; } = new Point(); public Size FrameSize { get; set; } = new Size(); @@ -94,10 +93,19 @@ namespace OpenDiablo2.SDL2_ var floor = floorLayer.Props[x + (y * mapData.Width)]; if (floor.Prop1 == 0) + { + texture = IntPtr.Zero; return; + } var sub_index = floor.Prop2; - var main_index = (floor.Prop3 / 16) + ((floor.Prop4 & 0x0F) * 16); + var main_index = (floor.Prop3 >> 4) + ((floor.Prop4 & 0x03) << 4); + if (mapData.DT1s[main_index] == null) + return; + + if (mapData.DT1s[main_index].Tiles.Count() <= sub_index) + return; + var tile = mapData.DT1s[main_index].Tiles[sub_index]; FrameSize = new Size(tile.Width, Math.Abs(tile.Height)); @@ -114,24 +122,33 @@ namespace OpenDiablo2.SDL2_ SDL.SDL_LockTexture(texture, IntPtr.Zero, out pixels, out pitch); try { - UInt32* data = (UInt32*)pixels; - for (var i = 0; i < FrameSize.Width * FrameSize.Height; i++) - data[i] = 0; - - - foreach (var block in tile.Blocks) + if (tile.Orientation == 0) { - var px = block.PositionX; - var py = FrameSize.Height + block.PositionY; - for (int yy = 0; yy < 32; yy++) + UInt32* data = (UInt32*)pixels; + for (var i = 0; i < FrameSize.Width * FrameSize.Height; i++) + data[i] = 0x0; + + foreach (var block in tile.Blocks) { - for (int xx = 0; xx < 32; xx++) + var px = block.PositionX; + var py = block.PositionY; + + //var px = 0; + //var py = 0; + for (int yy = 0; yy < 32; yy++) { - data[px + xx + ((py + yy) * pitch / 4)] = palette.Colors[block.PixelData[xx + (yy * 32)]]; + for (int xx = 0; xx < 32; xx++) + { + var index = px + xx + ((py + yy) * (pitch / 4)); + if (index > (FrameSize.Width * FrameSize.Height)) + continue; + if (index < 0) + continue; + data[index] = palette.Colors[block.PixelData[xx + ((31-yy) * 32)]]; + } } } } - } finally { @@ -142,7 +159,7 @@ namespace OpenDiablo2.SDL2_ internal Point GetRenderPoint() { return source == null - ? globalFrameOffset + ? Location : new Point(Location.X + source.Frames[Frame].OffsetX, (Location.Y - FrameSize.Height) + source.Frames[Frame].OffsetY); } diff --git a/OpenDiablo2.Scenes/Game.cs b/OpenDiablo2.Scenes/Game.cs index cce20d86..5fabc540 100644 --- a/OpenDiablo2.Scenes/Game.cs +++ b/OpenDiablo2.Scenes/Game.cs @@ -19,7 +19,7 @@ namespace OpenDiablo2.Scenes private readonly IResourceManager resourceManager; private GameState gameState; - private ISprite testSprite; + private ISprite[] testSprite; private ISprite panelSprite, healthManaSprite, gameGlobeOverlapSprite; @@ -39,10 +39,11 @@ namespace OpenDiablo2.Scenes if (gameState.MapDirty) RedrawMap(); - renderWindow.Draw(testSprite); + for (int i = 0; i < gameState.MapData.Width * gameState.MapData.Height; i++) + renderWindow.Draw(testSprite[i]); DrawPanel(); - + } private void DrawPanel() @@ -67,7 +68,7 @@ namespace OpenDiablo2.Scenes public void Update(long ms) { - + } public void Dispose() @@ -79,10 +80,18 @@ namespace OpenDiablo2.Scenes { gameState.MapDirty = false; - var x = 0; - var y = 0; - testSprite = renderWindow.GenerateMapCell(gameState.MapData, 0, 0, eRenderCellType.Floor, gameState.CurrentPalette); - testSprite.Location = new Point(((x * 80) - (y * 80)) + 100, ((x * 40) + (y * 40)) + 100); + testSprite = new ISprite[gameState.MapData.Width * gameState.MapData.Height]; + var idx = 0; + for (int y = 0; y < gameState.MapData.Height; y++) + { + for (int x = 0; x < gameState.MapData.Width; x++) + { + testSprite[idx] = renderWindow.GenerateMapCell(gameState.MapData, x, y, eRenderCellType.Floor, gameState.CurrentPalette); + testSprite[idx].Location = new Point(((x - y) * 80) - 900, ((x + y) * 40) - 1100); + idx++; + } + } + } } }