From c85b2bc605c6a42abefead3cfee9d77c22006cbb Mon Sep 17 00:00:00 2001 From: Tim Sarbin Date: Fri, 14 Dec 2018 14:48:36 -0500 Subject: [PATCH] Minor cleanup. Added object and object type data dictionaries. Map loader now includes object and tag data --- .../Interfaces/IEngineDataManager.cs | 6 +- OpenDiablo2.Common/Models/MPQDS1.cs | 101 +++++++++++---- OpenDiablo2.Common/Models/MPQDT1.cs | 42 +++---- OpenDiablo2.Common/Models/ObjectTypeInfo.cs | 21 ++++ OpenDiablo2.Common/OpenDiablo2.Common.csproj | 1 + OpenDiablo2.Core/AutofacModule.cs | 2 +- OpenDiablo2.Core/EngineDataManager.cs | 13 ++ OpenDiablo2.Core/GameState/GameState.cs | 58 +++++---- OpenDiablo2.Core/MPQProvider.cs | 77 ++++++------ OpenDiablo2.Core/Map Engine/MapEngine.cs | 119 ++++++++++-------- OpenDiablo2.SDL2/SDL2CharacterRenderer.cs | 4 +- OpenDiablo2.TestConsole/Program.cs | 4 +- 12 files changed, 277 insertions(+), 171 deletions(-) create mode 100644 OpenDiablo2.Common/Models/ObjectTypeInfo.cs diff --git a/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs b/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs index 2abc4d79..f380cb53 100644 --- a/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs +++ b/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Interfaces.Mobs; using OpenDiablo2.Common.Models; +using System.Collections.Immutable; namespace OpenDiablo2.Common.Interfaces { @@ -16,5 +15,6 @@ namespace OpenDiablo2.Common.Interfaces ImmutableDictionary HeroTypeConfigs { get; } ImmutableList EnemyTypeConfigs { get; } ImmutableList Objects { get; } + ImmutableList ObjectTypes { get; } } } diff --git a/OpenDiablo2.Common/Models/MPQDS1.cs b/OpenDiablo2.Common/Models/MPQDS1.cs index 1c23f911..f47e8d14 100644 --- a/OpenDiablo2.Common/Models/MPQDS1.cs +++ b/OpenDiablo2.Common/Models/MPQDS1.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Text; +using OpenDiablo2.Common.Exceptions; using OpenDiablo2.Common.Interfaces; namespace OpenDiablo2.Common.Models @@ -40,6 +42,11 @@ namespace OpenDiablo2.Common.Models public MPQDS1TileProps[] Props { get; internal set; } } + public sealed class MPQDS1TagLayer + { + public Int32 Number { get; internal set; } + } + public sealed class MPQDS1Object { public Int32 Type { get; internal set; } @@ -47,6 +54,7 @@ namespace OpenDiablo2.Common.Models public Int32 X { get; internal set; } public Int32 Y { get; internal set; } public Int32 DS1Flags { get; internal set; } + public ObjectInfo Info { get; internal set; } } public sealed class MPQDS1Group @@ -92,6 +100,7 @@ namespace OpenDiablo2.Common.Models public MPQDS1WallLayer[] WallLayers { get; internal set; } public MPQDS1FloorLayer[] FloorLayers { get; internal set; } public MPQDS1ShadowLayer[] ShadowLayers { get; internal set; } + public MPQDS1TagLayer[] TagLayers { get; internal set; } public MPQDS1Object[] Objects { get; internal set; } public MPQDS1Group[] Groups { get; internal set; } @@ -125,7 +134,7 @@ namespace OpenDiablo2.Common.Models fn.Append((char)b); } var fnStr = fn.ToString(); - if (fnStr.StartsWith("\\d2\\")) + if (fnStr.StartsWith(@"\d2\")) fnStr = fnStr.Substring(4); FileNames.Add(fnStr); } @@ -139,12 +148,7 @@ namespace OpenDiablo2.Common.Models if (Version >= 4) { NumberOfWalls = br.ReadInt32(); - - if (Version >= 16) - { - NumberOfFloors = br.ReadInt32(); - } - else NumberOfFloors = 1; + NumberOfFloors = Version >= 16 ? br.ReadInt32() : 1; } else { @@ -159,7 +163,7 @@ namespace OpenDiablo2.Common.Models if (Version < 4) { - layoutStream.AddRange(new int[] { 1, 9, 5, 12, 11 }); + layoutStream.AddRange(new[] { 1, 9, 5, 12, 11 }); } else { @@ -205,21 +209,27 @@ namespace OpenDiablo2.Common.Models }; } + TagLayers = new MPQDS1TagLayer[NumberOfTags]; + for (var l = 0; l < NumberOfTags; l++) + { + TagLayers[l] = new MPQDS1TagLayer { Number = -1 }; + } - for (int n = 0; n < layoutStream.Count; n++) + + foreach (var idx in layoutStream) { for (var y = 0; y < Height; y++) { for (var x = 0; x < Width; x++) { - switch (layoutStream[n]) + switch (idx) { // Walls case 1: case 2: case 3: case 4: - WallLayers[layoutStream[n] - 1].Props[x + (y * Width)] = new MPQDS1TileProps + WallLayers[idx - 1].Props[x + (y * Width)] = new MPQDS1TileProps { Prop1 = br.ReadByte(), Prop2 = br.ReadByte(), @@ -240,7 +250,7 @@ namespace OpenDiablo2.Common.Models } else { - WallLayers[layoutStream[n] - 5].Orientations[x + (y * Width)] = new MPQDS1WallOrientationTileProps + WallLayers[idx - 5].Orientations[x + (y * Width)] = new MPQDS1WallOrientationTileProps { Orientation1 = br.ReadByte(), Orientation2 = br.ReadByte(), @@ -253,7 +263,7 @@ namespace OpenDiablo2.Common.Models // Floors case 9: case 10: - FloorLayers[layoutStream[n] - 9].Props[x + (y * Width)] = new MPQDS1TileProps + FloorLayers[idx - 9].Props[x + (y * Width)] = new MPQDS1TileProps { Prop1 = br.ReadByte(), Prop2 = br.ReadByte(), @@ -263,7 +273,7 @@ namespace OpenDiablo2.Common.Models break; // Shadow case 11: - ShadowLayers[layoutStream[n] - 11].Props[x + (y * Width)] = new MPQDS1TileProps + ShadowLayers[idx - 11].Props[x + (y * Width)] = new MPQDS1TileProps { Prop1 = br.ReadByte(), Prop2 = br.ReadByte(), @@ -273,32 +283,80 @@ namespace OpenDiablo2.Common.Models break; // Tags case 12: - // TODO: Tags - br.ReadBytes(4); + TagLayers[idx - 12].Number = br.ReadInt32(); break; + default: + throw new OpenDiablo2Exception($"Unknown layer {idx} encountered."); } } } } + // Load the objects + NumberOfObjects = br.ReadInt32(); + Objects = new MPQDS1Object[NumberOfObjects]; + for (var i = 0; i < NumberOfObjects; i++) + { + Objects[i] = new MPQDS1Object + { + Type = br.ReadInt32(), + Id = br.ReadInt32(), + X = br.ReadInt32(), + Y = br.ReadInt32(), + }; + if (Version > 5) + Objects[i].DS1Flags = br.ReadInt32(); + + Objects[i].Info = engineDataManager.Objects.First(x => x.Id == Objects[i].Id); + } + + if (Version >= 12 && (TagType == 1 || TagType == 2)) + { + if (Version >= 18) + br.ReadInt32(); // Skip a byte (but why?) + + NumberOfGroups = br.ReadInt32(); + Groups = new MPQDS1Group[NumberOfGroups]; + for (var i = 0; i < NumberOfGroups; i++) + { + Groups[i] = new MPQDS1Group + { + TileX = br.ReadInt32(), + TileY = br.ReadInt32(), + Width = br.ReadInt32(), + Height = br.ReadInt32() + }; + if (Version >= 13) + br.ReadInt32(); // Unknown group property value (what is this???) + } + + } + else + Groups = new MPQDS1Group[0]; + + + if (Version >= 14) + { + // TODO: NPC Paths + } var dt1Mask = level.Dt1Mask; - for (int i = 0; i < 32; i++) + for (var i = 0; i < 32; i++) { var isMasked = ((dt1Mask >> i) & 1) == 1; if (!isMasked || levelType.File[i] == "0") continue; - DT1s[i] = resourceManager.GetMPQDT1("data\\global\\tiles\\" + levelType.File[i].Replace("/", "\\")); + DT1s[i] = resourceManager.GetMPQDT1(@"data\global\tiles\" + levelType.File[i].Replace("/", "\\")); } LookupTable = new List(); - foreach(var dt1 in DT1s.Where(x => x != null)) + foreach (var dt1 in DT1s.Where(x => x != null)) { - foreach(var tile in dt1.Tiles) + foreach (var tile in dt1.Tiles) { LookupTable.Add(new DS1LookupTable { @@ -311,7 +369,8 @@ namespace OpenDiablo2.Common.Models } } - + + } } } diff --git a/OpenDiablo2.Common/Models/MPQDT1.cs b/OpenDiablo2.Common/Models/MPQDT1.cs index 118007a0..cdced9fe 100644 --- a/OpenDiablo2.Common/Models/MPQDT1.cs +++ b/OpenDiablo2.Common/Models/MPQDT1.cs @@ -46,7 +46,7 @@ namespace OpenDiablo2.Common.Models public Int32 X2 { get; private set; } public Int32 NumberOfTiles { get; private set; } public MPQDT1Tile[] Tiles { get; private set; } - private readonly Int32 tileHeaderOffset; + private readonly Int32 _tileHeaderOffset; public MPQDT1(Stream stream) { @@ -58,19 +58,19 @@ namespace OpenDiablo2.Common.Models stream.Seek(268, SeekOrigin.Begin); // Skip useless header info NumberOfTiles = br.ReadInt32(); - tileHeaderOffset = br.ReadInt32(); - + _tileHeaderOffset = br.ReadInt32(); + ReadTiles(br); ReadBlockHeaders(br); ReadBlockGraphics(br); - + } private void ReadTiles(BinaryReader br) { Tiles = new MPQDT1Tile[NumberOfTiles]; - for (int tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++) + for (var tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++) { - br.BaseStream.Seek(tileHeaderOffset + (tileIndex * 96), SeekOrigin.Begin); + br.BaseStream.Seek(_tileHeaderOffset + (tileIndex * 96), SeekOrigin.Begin); Tiles[tileIndex] = new MPQDT1Tile(); var tile = Tiles[tileIndex]; @@ -86,7 +86,7 @@ namespace OpenDiablo2.Common.Models tile.SubIndex = br.ReadInt32(); tile.RarityOrFrameIndex = br.ReadInt32(); br.ReadBytes(4); - for (int i = 0; i < 25; i++) + for (var i = 0; i < 25; i++) tile.SubTileFlags[i] = br.ReadByte(); br.ReadBytes(7); tile.BlockHeadersPointer = br.ReadInt32(); @@ -99,12 +99,12 @@ namespace OpenDiablo2.Common.Models private void ReadBlockHeaders(BinaryReader br) { - for (int tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++) + for (var tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++) { var tile = Tiles[tileIndex]; tile.Blocks = new MPQDT1Block[tile.NumberOfBlocks]; - for (int blockIndex = 0; blockIndex < tile.NumberOfBlocks; blockIndex++) + for (var blockIndex = 0; blockIndex < tile.NumberOfBlocks; blockIndex++) { br.BaseStream.Seek(tile.BlockHeadersPointer + (blockIndex * 20), SeekOrigin.Begin); @@ -126,9 +126,9 @@ namespace OpenDiablo2.Common.Models private void ReadBlockGraphics(BinaryReader br) { - for (int tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++) + for (var tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++) { - for (int blockIndex = 0; blockIndex < Tiles[tileIndex].NumberOfBlocks; blockIndex++) + for (var blockIndex = 0; blockIndex < Tiles[tileIndex].NumberOfBlocks; blockIndex++) { var block = Tiles[tileIndex].Blocks[blockIndex]; br.BaseStream.Seek(Tiles[tileIndex].BlockHeadersPointer + block.FileOffset, SeekOrigin.Begin); @@ -139,17 +139,13 @@ namespace OpenDiablo2.Common.Models if (block.Length != 256) throw new OpenDiablo2Exception($"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 y = 0; var length = 256; block.PixelData = new Int16[32 * 32]; while (length > 0) { - x = xjump[y]; - n = nbpix[y]; + var x = new[] { 14, 12, 10, 8, 6, 4, 2, 0, 2, 4, 6, 8, 10, 12, 14 }[y]; + var n = new[] { 4, 8, 12, 16, 20, 24, 28, 32, 28, 24, 20, 16, 12, 8, 4 }[y]; length -= n; while (n > 0) { @@ -165,15 +161,13 @@ namespace OpenDiablo2.Common.Models { // RLE block var length = block.Length; - byte b1; - byte b2; - int x = 0; - int y = 0; + var x = 0; + var y = 0; block.PixelData = new Int16[32 * 32]; while (length > 0) { - b1 = br.ReadByte(); - b2 = br.ReadByte(); + var b1 = br.ReadByte(); + var b2 = br.ReadByte(); length -= 2; if (b1 + b2 == 0) { diff --git a/OpenDiablo2.Common/Models/ObjectTypeInfo.cs b/OpenDiablo2.Common/Models/ObjectTypeInfo.cs new file mode 100644 index 00000000..47240c09 --- /dev/null +++ b/OpenDiablo2.Common/Models/ObjectTypeInfo.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenDiablo2.Common.Models +{ + public sealed class ObjectTypeInfo + { + public string Name { get; internal set; } + public string Token { get; internal set; } + public bool Beta { get; internal set; } + } + + public static class ObjectTypeInfoHelper + { + public static ObjectTypeInfo ToObjectTypeInfo(this string[] row) + => new ObjectTypeInfo { Name = row[0], Token = row[1], Beta = Convert.ToInt32(row[2]) == 1 }; + } +} diff --git a/OpenDiablo2.Common/OpenDiablo2.Common.csproj b/OpenDiablo2.Common/OpenDiablo2.Common.csproj index dad7635f..47fc1aa0 100644 --- a/OpenDiablo2.Common/OpenDiablo2.Common.csproj +++ b/OpenDiablo2.Common/OpenDiablo2.Common.csproj @@ -121,6 +121,7 @@ + diff --git a/OpenDiablo2.Core/AutofacModule.cs b/OpenDiablo2.Core/AutofacModule.cs index 124c087d..0c028c39 100644 --- a/OpenDiablo2.Core/AutofacModule.cs +++ b/OpenDiablo2.Core/AutofacModule.cs @@ -45,7 +45,7 @@ namespace OpenDiablo2.Core builder.RegisterType().AsImplementedInterfaces().InstancePerDependency(); builder.RegisterType().AsImplementedInterfaces().InstancePerDependency(); builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().InstancePerDependency(); diff --git a/OpenDiablo2.Core/EngineDataManager.cs b/OpenDiablo2.Core/EngineDataManager.cs index 9df724b2..6a9befe3 100644 --- a/OpenDiablo2.Core/EngineDataManager.cs +++ b/OpenDiablo2.Core/EngineDataManager.cs @@ -23,6 +23,7 @@ namespace OpenDiablo2.Core public ImmutableList LevelPresets { get; internal set; } public ImmutableList LevelTypes { get; internal set; } public ImmutableList Objects { get; internal set; } + public ImmutableList ObjectTypes { get; internal set; } public ImmutableList Items { get; internal set; } public ImmutableDictionary ExperienceConfigs { get; internal set; } @@ -83,6 +84,18 @@ namespace OpenDiablo2.Core .Where(x => x.Count() > 150 && x[0] != "Expansion") .Select(x => x.ToObjectInfo()) .ToImmutableList(); + + log.Info("Loading object types"); + ObjectTypes = mpqProvider + .GetTextFile(ResourcePaths.ObjectDetails) + .Skip(1) + .Where(x => !String.IsNullOrWhiteSpace(x)) + .Select(x => x.Split('\t')) + .Where(x => x.Count() == 3 && x[0] != "Expansion") + .Select(x => x.ToObjectTypeInfo()) + .ToImmutableList(); + + } private ImmutableList LoadItemData() diff --git a/OpenDiablo2.Core/GameState/GameState.cs b/OpenDiablo2.Core/GameState/GameState.cs index c15a76e6..2bd685cb 100644 --- a/OpenDiablo2.Core/GameState/GameState.cs +++ b/OpenDiablo2.Core/GameState/GameState.cs @@ -26,15 +26,15 @@ namespace OpenDiablo2.Core.GameState_ private readonly Func getSessionManager; private readonly Func getRandomizedMapGenerator; - private float animationTime = 0f; + private float animationTime; private List mapInfo; - private readonly List mapDataLookup = new List(); + private readonly List mapDataLookup; private ISessionManager sessionManager; public int Act { get; private set; } public string MapName { get; private set; } public Palette CurrentPalette => paletteProvider.PaletteTable[$"ACT{Act}"]; - public List PlayerInfos { get; private set; } = new List(); + public List PlayerInfos { get; private set; } readonly private IMouseCursor originalMouseCursor; @@ -74,7 +74,8 @@ namespace OpenDiablo2.Core.GameState_ this.getRandomizedMapGenerator = getRandomizedMapGenerator; originalMouseCursor = renderWindow.MouseCursor; - + PlayerInfos = new List(); + mapDataLookup = new List(); } public void Initialize(string characterName, eHero hero, eSessionType sessionType) @@ -88,8 +89,8 @@ namespace OpenDiablo2.Core.GameState_ sessionManager.OnFocusOnPlayer += OnFocusOnPlayer; mapInfo = new List(); + sceneManager.ChangeScene(eSceneType.Game); - sessionManager.JoinGame(characterName, hero); } @@ -97,7 +98,7 @@ namespace OpenDiablo2.Core.GameState_ => getMapEngine().FocusedPlayerId = playerId; private void OnPlayerInfo(int clientHash, IEnumerable playerInfo) - => this.PlayerInfos = playerInfo.ToList(); + => PlayerInfos = playerInfo.ToList(); private void OnLocatePlayers(int clientHash, IEnumerable playerLocationDetails) { @@ -203,9 +204,12 @@ namespace OpenDiablo2.Core.GameState_ var random = new Random(Seed); - //var mapName = "data\\global\\tiles\\" + mapNames[random.Next(mapNames.Count)].Replace("/", "\\"); + // ------------------------------------------------------------------------------------- + // var mapName = "data\\global\\tiles\\" + mapNames[random.Next(mapNames.Count)].Replace("/", "\\"); + // ------------------------------------------------------------------------------------- // TODO: ***TEMP FOR TESTING var mapName = "data\\global\\tiles\\" + mapNames[0].Replace("/", "\\"); + // ------------------------------------------------------------------------------------- MapName = levelDetails.LevelPreset.Name; Act = levelDetails.LevelType.Act; @@ -270,15 +274,23 @@ namespace OpenDiablo2.Core.GameState_ private IMapInfo GetMap(ref int cellX, ref int cellY) { var p = new Point(cellX, cellY); - var map = mapInfo.LastOrDefault(z => z.TileLocation.Contains(p)); - if (map == null) - { - return null; - } - cellX -= map.TileLocation.X; - cellY -= map.TileLocation.Y; - return map; + IMapInfo mi = null; + for (var i = mapInfo.Count - 1; i >= 0; i--) + { + if (mapInfo[i].TileLocation.Contains(p)) + { + mi = mapInfo[i]; + break; + } + + } + if (mi == null) + return null; + + cellX -= mi.TileLocation.X; + cellY -= mi.TileLocation.Y; + return mi; } public void UpdateMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType, IEnumerable mapCellInfo) @@ -294,9 +306,8 @@ namespace OpenDiablo2.Core.GameState_ } else { - var cursorsprite = renderWindow.LoadSprite(ResourcePaths.GeneratePathForItem(item.Item.InvFile), Palettes.Units); - - renderWindow.MouseCursor = renderWindow.LoadCursor(cursorsprite, 0, new Point(cursorsprite.FrameSize.Width / 2, cursorsprite.FrameSize.Height / 2)); + var cursorSprite = renderWindow.LoadSprite(ResourcePaths.GeneratePathForItem(item.Item.InvFile), Palettes.Units); + renderWindow.MouseCursor = renderWindow.LoadCursor(cursorSprite, 0, new Point(cursorSprite.FrameSize.Width / 2, cursorSprite.FrameSize.Height / 2)); } this.SelectedItem = item; @@ -317,8 +328,8 @@ namespace OpenDiablo2.Core.GameState_ if (cellInfo != null && (cellInfo.Ignore || !cellInfo.Tile.Animated)) return cellInfo.Ignore ? null : cellInfo; - var main_index = (props.Prop3 >> 4) + ((props.Prop4 & 0x03) << 4); - var sub_index = props.Prop2; + var mainIndex = (props.Prop3 >> 4) + ((props.Prop4 & 0x03) << 4); + var subIndex = props.Prop2; if (orientation == 0) { @@ -375,15 +386,14 @@ namespace OpenDiablo2.Core.GameState_ } } - int frame = 0; IEnumerable tiles = Enumerable.Empty(); tiles = map.FileData.LookupTable - .Where(x => x.MainIndex == main_index && x.SubIndex == sub_index && x.Orientation == orientation) + .Where(x => x.MainIndex == mainIndex && x.SubIndex == subIndex && x.Orientation == orientation) .Select(x => x.TileRef); if (tiles == null || !tiles.Any()) { - log.Error($"Could not find tile [{main_index}:{sub_index}:{orientation}]!"); + log.Error($"Could not find tile [{mainIndex}:{subIndex}:{orientation}]!"); map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true }; return null; } @@ -438,7 +448,7 @@ namespace OpenDiablo2.Core.GameState_ } - var mapCellInfo = mapDataLookup.FirstOrDefault(x => x.Tile.Id == tile.Id && x.AnimationId == frame); + var mapCellInfo = mapDataLookup.FirstOrDefault(x => x.Tile.Id == tile.Id); if (mapCellInfo == null) { mapCellInfo = renderWindow.CacheMapCell(tile, cellType); diff --git a/OpenDiablo2.Core/MPQProvider.cs b/OpenDiablo2.Core/MPQProvider.cs index 586ea67a..e88d23b6 100644 --- a/OpenDiablo2.Core/MPQProvider.cs +++ b/OpenDiablo2.Core/MPQProvider.cs @@ -27,45 +27,52 @@ using System.Linq; namespace OpenDiablo2.Core { - public sealed class MPQProvider : IMPQProvider + public sealed class MpqProvider : IMPQProvider { - private readonly IList mpqs; - private readonly Dictionary mpqLookup = new Dictionary(); + private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + private readonly IList _mpqs; + private readonly Dictionary _mpqLookup = new Dictionary(); - public MPQProvider(GlobalConfiguration globalConfiguration) + public MpqProvider(GlobalConfiguration globalConfiguration) { // TODO: Make this less dumb. We need to an external file to configure mpq load order. - this.mpqs = Directory + _mpqs = Directory .EnumerateFiles(globalConfiguration.BaseDataPath, "*.mpq") - .Where(x => !Path.GetFileName(x).StartsWith("patch")) + .Where(x => !(Path.GetFileName(x)?.StartsWith("patch") ?? false)) .Select(file => new MPQ(file)) .ToArray(); - + + if (!_mpqs.Any()) + { + _log.Fatal("No data files were found! Are you specifying the correct data path?"); + throw new OpenDiablo2Exception("No data files were found."); + } // Load the base game files - for(var i = 0; i < mpqs.Count(); i++) + for(var i = 0; i < _mpqs.Count; i++) { - if (Path.GetFileName(mpqs[i].Path).StartsWith("d2exp") || Path.GetFileName(mpqs[i].Path).StartsWith("d2x")) + var path = Path.GetFileName(_mpqs[i].Path) ?? string.Empty; + + if (path.StartsWith("d2exp") || path.StartsWith("d2x")) continue; - foreach(var file in mpqs[i].Files) - { - mpqLookup[file.ToLower()] = i; - } + foreach(var file in _mpqs[i].Files) + _mpqLookup[file.ToLower()] = i; } // Load the expansion game files - for (var i = 0; i < mpqs.Count(); i++) + for (var i = 0; i < _mpqs.Count; i++) { - if (!Path.GetFileName(mpqs[i].Path).StartsWith("d2exp") && !Path.GetFileName(mpqs[i].Path).StartsWith("d2x")) + var path = Path.GetFileName(_mpqs[i].Path) ?? string.Empty; + + if (!path.StartsWith("d2exp") && !path.StartsWith("d2x")) continue; - foreach (var file in mpqs[i].Files) - { - mpqLookup[file.ToLower()] = i; - } + foreach (var file in _mpqs[i].Files) + _mpqLookup[file.ToLower()] = i; } } @@ -77,36 +84,26 @@ namespace OpenDiablo2.Core return result; } - public IEnumerator GetEnumerator() - { - return mpqs.GetEnumerator(); - } + public IEnumerator GetEnumerator() => _mpqs.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public Stream GetStream(string fileName) - { - if (!mpqLookup.ContainsKey(fileName.ToLower())) - return null; + public Stream GetStream(string fileName) => !_mpqLookup.ContainsKey(fileName.ToLower()) + ? null + : _mpqs[_mpqLookup[fileName.ToLower()]].OpenFile(fileName); - return mpqs[mpqLookup[fileName.ToLower()]].OpenFile(fileName); - } - public IEnumerable GetTextFile(string fileName) - => new StreamReader(mpqs[mpqLookup[fileName.ToLower()]].OpenFile(fileName)).ReadToEnd().Split('\n'); + => new StreamReader(_mpqs[_mpqLookup[fileName.ToLower()]].OpenFile(fileName)).ReadToEnd().Split('\n'); public string GetCharacterDccPath(eHero hero, eMobMode mobMode, eCompositType compositType, PlayerEquipment equipment) { - var fileName = ($@"{ResourcePaths.PlayerAnimationBase}\{hero.ToToken()}\{compositType.ToToken()}\{hero.ToToken()}{compositType.ToToken()}").ToLower(); + var fileName = $@"{ResourcePaths.PlayerAnimationBase}\{hero.ToToken()}\{compositType.ToToken()}\{hero.ToToken()}{compositType.ToToken()}".ToLower(); switch (compositType) { case eCompositType.Head: fileName += $"{equipment.Head?.Item.Code ?? eArmorType.Lite.ToToken()}{mobMode.ToToken()}"; - return mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower()) + return _mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower()) ? $"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower() : $"{fileName}{eWeaponClass.HandToHand.ToToken()}.dcc".ToLower(); case eCompositType.Torso: @@ -114,7 +111,7 @@ namespace OpenDiablo2.Core case eCompositType.RightArm: case eCompositType.LeftArm: fileName += $"{equipment.ArmorType.ToToken()}{mobMode.ToToken()}"; - return mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower()) + return _mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower()) ? $"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower() : $"{fileName}{eWeaponClass.HandToHand.ToToken()}.dcc".ToLower(); case eCompositType.RightHand: @@ -131,14 +128,14 @@ namespace OpenDiablo2.Core if (!(equipment.LeftArm?.Item is Armor)) return null; fileName += $"{equipment.LeftArm.Item.Code}{mobMode.ToToken()}"; - return mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower()) + return _mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower()) ? $"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower() : $"{fileName}{eWeaponClass.HandToHand.ToToken()}.dcc".ToLower(); // TODO: Figure these out... case eCompositType.Special1: case eCompositType.Special2: fileName += $"{equipment.ArmorType.ToToken()}{mobMode.ToToken()}{equipment.WeaponClass}.dcc".ToLower(); - return mpqLookup.ContainsKey(fileName) + return _mpqLookup.ContainsKey(fileName) ? fileName : null; // TODO: Should we silence this? case eCompositType.Special3: diff --git a/OpenDiablo2.Core/Map Engine/MapEngine.cs b/OpenDiablo2.Core/Map Engine/MapEngine.cs index 6fb2c680..309f4bee 100644 --- a/OpenDiablo2.Core/Map Engine/MapEngine.cs +++ b/OpenDiablo2.Core/Map Engine/MapEngine.cs @@ -11,29 +11,32 @@ namespace OpenDiablo2.Core.Map_Engine { public sealed class MapEngine : IMapEngine { - private readonly IGameState gameState; - private readonly IRenderWindow renderWindow; + private readonly IGameState _gameState; + private readonly IRenderWindow _renderWindow; - private readonly List characterRenderers = new List(); + private readonly List _characterRenderers = new List(); public int FocusedPlayerId { get; set; } = 0; - private PointF cameraLocation = new PointF(); + private PointF _cameraLocation = new PointF(); public PointF CameraLocation { - get => cameraLocation; + get => _cameraLocation; set { - if (cameraLocation == value) + // ReSharper disable once RedundantCheckBeforeAssignment (This is a false positive) + if (_cameraLocation == value) return; - cameraLocation = value; + _cameraLocation = value; } } private const int - cellSizeX = 160, - cellSizeY = 80; + CellSizeX = 160, + CellSizeY = 80, + CellSizeXHalf = 80, + CellSizeYHalf = 40; public MapEngine( IGameState gameState, @@ -41,8 +44,8 @@ namespace OpenDiablo2.Core.Map_Engine ISessionManager sessionManager ) { - this.gameState = gameState; - this.renderWindow = renderWindow; + _gameState = gameState; + _renderWindow = renderWindow; sessionManager.OnPlayerInfo += OnPlayerInfo; sessionManager.OnLocatePlayers += OnLocatePlayers; @@ -52,7 +55,12 @@ namespace OpenDiablo2.Core.Map_Engine { foreach (var loc in playerLocationDetails) { - var cr = characterRenderers.FirstOrDefault(x => x.LocationDetails.PlayerId == loc.PlayerId); + var cr = _characterRenderers.FirstOrDefault(x => x.LocationDetails.PlayerId == loc.PlayerId); + if (cr == null) + { + // TODO: Should we log this? + continue; + } var newDirection = loc.MovementDirection != cr.LocationDetails.MovementDirection; var stanceChanged = loc.MovementType != cr.LocationDetails.MovementType; cr.LocationDetails = loc; @@ -64,10 +72,10 @@ namespace OpenDiablo2.Core.Map_Engine private void OnPlayerInfo(int clientHash, IEnumerable playerInfo) { // Remove character renderers for players that no longer exist... - characterRenderers.RemoveAll(x => playerInfo.Any(z => z.UID == x.UID)); + _characterRenderers.RemoveAll(x => playerInfo.Any(z => z.UID == x.UID)); // Update existing character renderers - foreach (var cr in characterRenderers) + foreach (var cr in _characterRenderers) { var info = playerInfo.FirstOrDefault(x => x.UID == cr.UID); if (info == null) @@ -82,67 +90,70 @@ namespace OpenDiablo2.Core.Map_Engine } // Add character renderers for characters that now exist - foreach (var info in playerInfo.Where(x => !characterRenderers.Any(z => x.UID == z.UID))) + foreach (var info in playerInfo.Where(x => _characterRenderers.All(z => x.UID != z.UID)).ToArray()) { - var cr = renderWindow.CreateCharacterRenderer(); + var cr = _renderWindow.CreateCharacterRenderer(); cr.UID = info.UID; cr.LocationDetails = info.LocationDetails; cr.MobMode = info.MobMode; cr.Equipment = info.Equipment; cr.Hero = info.Hero; - characterRenderers.Add(cr); + _characterRenderers.Add(cr); cr.ResetAnimationData(); } } - const int skewX = 400; - const int skewY = 300; + private const int SkewX = 400; + private const int SkewY = 300; public void Render() { - var xOffset = gameState.CameraOffset; + var xOffset = _gameState.CameraOffset; - var cx = -(cameraLocation.X - Math.Truncate(cameraLocation.X)); - var cy = -(cameraLocation.Y - Math.Truncate(cameraLocation.Y)); + var cx = -(_cameraLocation.X - Math.Truncate(_cameraLocation.X)); + var cy = -(_cameraLocation.Y - Math.Truncate(_cameraLocation.Y)); - for (int ty = -7; ty <= 9; ty++) + for (var ty = -7; ty <= 9; ty++) { - for (int tx = -8; tx <= 8; tx++) + for (var tx = -8; tx <= 8; tx++) { - var ax = tx + Math.Truncate(cameraLocation.X); - var ay = ty + Math.Truncate(cameraLocation.Y); + var ax = (int)(tx + Math.Truncate(_cameraLocation.X)); + var ay = (int)(ty + Math.Truncate(_cameraLocation.Y)); - var px = (tx - ty) * (cellSizeX / 2); - var py = (tx + ty) * (cellSizeY / 2); + var px = (tx - ty) * CellSizeXHalf; + var py = (tx + ty) * CellSizeYHalf; - var ox = (cx - cy) * (cellSizeX / 2); - var oy = (cx + cy) * (cellSizeY / 2); + var ox = (cx - cy) * CellSizeXHalf; + var oy = (cx + cy) * CellSizeYHalf; - foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.WallLower)) - renderWindow.DrawMapCell(cellInfo, skewX + px + (int)ox + xOffset, skewY + py + (int)oy + 80); + foreach (var cellInfo in _gameState.GetMapCellInfo(ax, ay, eRenderCellType.WallLower)) + _renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy + 80); - foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.Floor)) - renderWindow.DrawMapCell(cellInfo, skewX + px + (int)ox + xOffset, skewY + py + (int)oy); + foreach (var cellInfo in _gameState.GetMapCellInfo(ax, ay, eRenderCellType.Floor)) + _renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy); - foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.WallNormal)) - renderWindow.DrawMapCell(cellInfo, skewX + px + (int)ox + xOffset, skewY + py + (int)oy + 80); + foreach (var cellInfo in _gameState.GetMapCellInfo(ax, ay, eRenderCellType.WallNormal)) + _renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy + 80); - foreach (var character in characterRenderers.Where(x => Math.Truncate(x.LocationDetails.PlayerX) == ax && Math.Truncate(x.LocationDetails.PlayerY) == ay)) + foreach (var character in _characterRenderers.Where(x => + (int)Math.Truncate(x.LocationDetails.PlayerX) == ax && + (int)Math.Truncate(x.LocationDetails.PlayerY) == ay) + ) { - var ptx = character.LocationDetails.PlayerX - Math.Truncate(cameraLocation.X); - var pty = character.LocationDetails.PlayerY - Math.Truncate(cameraLocation.Y); + var ptx = character.LocationDetails.PlayerX - Math.Truncate(_cameraLocation.X); + var pty = character.LocationDetails.PlayerY - Math.Truncate(_cameraLocation.Y); - var ppx = (ptx - pty) * (cellSizeX / 2); - var ppy = (ptx + pty) * (cellSizeY / 2); + var ppx = (int)((ptx - pty) * CellSizeXHalf); + var ppy = (int)((ptx + pty) * CellSizeYHalf); - character.Render(skewX + (int)ppx + (int)ox + xOffset, skewY + (int)ppy + (int)oy); + character.Render(SkewX + (int)ppx + (int)ox + xOffset, SkewY + (int)ppy + (int)oy); } - foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.Roof)) - renderWindow.DrawMapCell(cellInfo, skewX + px + (int)ox + xOffset, skewY + py + (int)oy - 80); + foreach (var cellInfo in _gameState.GetMapCellInfo(ax, ay, eRenderCellType.Roof)) + _renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy - 80); } } @@ -154,18 +165,18 @@ namespace OpenDiablo2.Core.Map_Engine public void Update(long ms) { - foreach (var character in characterRenderers) + foreach (var character in _characterRenderers) character.Update(ms); - if (FocusedPlayerId != 0) - { - var player = gameState.PlayerInfos.FirstOrDefault(x => x.LocationDetails.PlayerId == FocusedPlayerId); - if (player != null) - { - // TODO: Maybe smooth movement? Maybe not? - CameraLocation = new PointF(player.LocationDetails.PlayerX, player.LocationDetails.PlayerY); - } - } + if (FocusedPlayerId == 0) + return; + + var player = _gameState.PlayerInfos.FirstOrDefault(x => x.LocationDetails.PlayerId == FocusedPlayerId); + if (player == null) + return; + + CameraLocation = new PointF(player.LocationDetails.PlayerX, player.LocationDetails.PlayerY); + } diff --git a/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs b/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs index 0a77c14b..997aebf4 100644 --- a/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs +++ b/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs @@ -189,8 +189,8 @@ namespace OpenDiablo2.SDL2_ { var texture = SDL.SDL_CreateTexture(renderer, SDL.SDL_PIXELFORMAT_ARGB8888, (int)SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, frameW, frameH); - SDL.SDL_LockTexture(texture, IntPtr.Zero, out IntPtr pixels, out int pitch); - UInt32* data = (UInt32*)pixels; + SDL.SDL_LockTexture(texture, IntPtr.Zero, out var pixels, out var pitch); + var data = (UInt32*)pixels; var priorityBase = (directionConversion[LocationDetails.MovementDirection] * animationData.FramesPerDirection * animationData.NumberOfLayers) + (frameIndex * animationData.NumberOfLayers); diff --git a/OpenDiablo2.TestConsole/Program.cs b/OpenDiablo2.TestConsole/Program.cs index da6719d1..b5c7728f 100644 --- a/OpenDiablo2.TestConsole/Program.cs +++ b/OpenDiablo2.TestConsole/Program.cs @@ -11,7 +11,7 @@ namespace OpenDiablo2.TestConsole static class Program { private static GlobalConfiguration GlobalConfig = null; - private static MPQProvider MPQProv = null; + private static MpqProvider MPQProv = null; private static EngineDataManager EngineDataMan = null; static void Main(string[] args) @@ -126,7 +126,7 @@ namespace OpenDiablo2.TestConsole BaseDataPath = Path.GetFullPath(path) }; - MPQProv = new MPQProvider(GlobalConfig); + MPQProv = new MpqProvider(GlobalConfig); EngineDataMan = new EngineDataManager(MPQProv); }