diff --git a/OpenDiablo2.Common/Interfaces/Drawing/ICharacterRenderer.cs b/OpenDiablo2.Common/Interfaces/Drawing/ICharacterRenderer.cs new file mode 100644 index 00000000..8f2cac23 --- /dev/null +++ b/OpenDiablo2.Common/Interfaces/Drawing/ICharacterRenderer.cs @@ -0,0 +1,20 @@ +using System; +using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Models; + +namespace OpenDiablo2.Common.Interfaces.Drawing +{ + public interface ICharacterRenderer : IDisposable + { + Guid UID { get; set; } + PlayerLocationDetails LocationDetails { get; set; } + eHero Hero { get; set; } + eWeaponClass WeaponClass { get; set; } + eArmorType ArmorType { get; set; } + eMobMode MobMode { get; set; } + + void Update(long ms); + void Render(int pixelOffsetX, int pixelOffsetY); + void ResetAnimationData(); + } +} diff --git a/OpenDiablo2.Common/Interfaces/Drawing/IRenderWindow.cs b/OpenDiablo2.Common/Interfaces/Drawing/IRenderWindow.cs index e87979a9..5fdb0fe5 100644 --- a/OpenDiablo2.Common/Interfaces/Drawing/IRenderWindow.cs +++ b/OpenDiablo2.Common/Interfaces/Drawing/IRenderWindow.cs @@ -1,5 +1,6 @@ using System; using System.Drawing; +using OpenDiablo2.Common.Interfaces.Drawing; using OpenDiablo2.Common.Models; namespace OpenDiablo2.Common.Interfaces @@ -29,5 +30,6 @@ namespace OpenDiablo2.Common.Interfaces void Draw(ILabel label); MapCellInfo CacheMapCell(MPQDT1Tile mapCell); void DrawMapCell(MapCellInfo mapCellInfo, int xPixel, int yPixel); + ICharacterRenderer CreateCharacterRenderer(); } } diff --git a/OpenDiablo2.Common/Models/BitMuncher.cs b/OpenDiablo2.Common/Models/BitMuncher.cs index 86215928..8702759e 100644 --- a/OpenDiablo2.Common/Models/BitMuncher.cs +++ b/OpenDiablo2.Common/Models/BitMuncher.cs @@ -21,9 +21,9 @@ namespace OpenDiablo2.Common.Models this.data = source.data; this.Offset = source.Offset; } - public int GetBit() + public UInt32 GetBit() { - var result = (data[Offset / 8] >> (Offset % 8)) & 0x01; + var result = (UInt32)(data[Offset / 8] >> (Offset % 8)) & 0x01; Offset++; BitsRead++; return result; @@ -33,10 +33,14 @@ namespace OpenDiablo2.Common.Models public byte GetByte() => (byte)GetBits(8); public Int32 GetInt32() => MakeSigned(GetBits(32), 32); + public UInt32 GetUInt32() => GetBits(32); - public int GetBits(int bits) + public UInt32 GetBits(int bits) { - var result = 0; + if (bits == 0) + return 0; + + var result = 0U; for (var i = 0; i < bits; i++) result |= (GetBit() << i); @@ -45,14 +49,21 @@ namespace OpenDiablo2.Common.Models public int GetSignedBits(int bits) => MakeSigned(GetBits(bits), bits); - private int MakeSigned(int value, int bits) + + private Int32 MakeSigned(UInt32 value, int bits) { + if (bits == 0) + return 0; + // If its a single bit, a value of 1 is -1 automagically if (bits == 1) - return -value; + return -(Int32)value; + // If there is no sign bit, return the value as is if ((value & (1 << (bits - 1))) == 0) - return value; + return (Int32)value; + // We need to extend the signed bit out so that the negative value + // representation still works with the 2s compliment rule. var result = UInt32.MaxValue; for (byte i = 0; i < bits; i++) { @@ -60,8 +71,7 @@ namespace OpenDiablo2.Common.Models result -= (UInt32)(1 << i); } - var newResult = (int)result; - return newResult; + return (Int32)result; // Force casting to a signed value } } diff --git a/OpenDiablo2.Common/Models/MPQCOF.cs b/OpenDiablo2.Common/Models/MPQCOF.cs index 5ff25880..277e47e7 100644 --- a/OpenDiablo2.Common/Models/MPQCOF.cs +++ b/OpenDiablo2.Common/Models/MPQCOF.cs @@ -20,7 +20,10 @@ namespace OpenDiablo2.Common.Models 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"; + { + var result = $"{ResourcePaths.PlayerAnimationBase}\\{COF.Hero.ToToken()}\\{CompositType.ToToken()}\\{COF.Hero.ToToken()}{CompositType.ToToken()}{armorType.ToToken()}{COF.MobMode.ToToken()}{COF.WeaponClass.ToToken()}.dcc"; + return result; + } } diff --git a/OpenDiablo2.Common/Models/MPQDCC.cs b/OpenDiablo2.Common/Models/MPQDCC.cs index 5a40d06a..e0856086 100644 --- a/OpenDiablo2.Common/Models/MPQDCC.cs +++ b/OpenDiablo2.Common/Models/MPQDCC.cs @@ -42,12 +42,12 @@ namespace OpenDiablo2.Common.Models public MPQDCCDirectionFrame(BitMuncher bits, MPQDCCDirection direction) { var variable0 = bits.GetBits(direction.Variable0Bits); - Width = bits.GetBits(direction.WidthBits); - Height = bits.GetBits(direction.HeightBits); + Width = (int)bits.GetBits(direction.WidthBits); + Height = (int)bits.GetBits(direction.HeightBits); XOffset = bits.GetSignedBits(direction.XOffsetBits); YOffset = bits.GetSignedBits(direction.YOffsetBits); - NumberOfOptionalBytes = bits.GetBits(direction.OptionalDataBits); - NumberOfCodedBytes = bits.GetBits(direction.CodedBytesBits); + NumberOfOptionalBytes = (int)bits.GetBits(direction.OptionalDataBits); + NumberOfCodedBytes = (int)bits.GetBits(direction.CodedBytesBits); FrameIsBottomUp = bits.GetBit() == 1; Box = new Rectangle( @@ -131,7 +131,7 @@ namespace OpenDiablo2.Common.Models } public sealed class MPQDCCDirection { - const int DCC_MAX_PB_ENTRY = 85000; // But why is this the magic number? + const int DCC_MAX_PB_ENTRY = 8500; // But why is this the magic number? public int OutSizeCoded { get; private set; } public int CompressionFlags { get; private set; } public int Variable0Bits { get; private set; } @@ -146,7 +146,7 @@ namespace OpenDiablo2.Common.Models 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 byte[] PaletteEntries { get; private set; } public Rectangle Box { get; private set; } public Cell[] Cells { get; private set; } public int HorizontalCellCount { get; private set; } @@ -156,8 +156,8 @@ namespace OpenDiablo2.Common.Models 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); + OutSizeCoded = (int)bm.GetUInt32(); + CompressionFlags = (int)bm.GetBits(2); Variable0Bits = crazyBitTable[bm.GetBits(4)]; WidthBits = crazyBitTable[bm.GetBits(4)]; HeightBits = crazyBitTable[bm.GetBits(4)]; @@ -168,30 +168,41 @@ namespace OpenDiablo2.Common.Models Frames = new MPQDCCDirectionFrame[file.NumberOfFrames]; + var minx = long.MaxValue; + var miny = long.MaxValue; + var maxx = long.MinValue; + var maxy = long.MinValue; // Load the frame headers for (var frameIdx = 0; frameIdx < file.NumberOfFrames; frameIdx++) + { Frames[frameIdx] = new MPQDCCDirectionFrame(bm, this); + minx = Math.Min(Frames[frameIdx].Box.X, minx); + miny = Math.Min(Frames[frameIdx].Box.Y, miny); + maxx = Math.Max(Frames[frameIdx].Box.Right, maxx); + maxy = Math.Max(Frames[frameIdx].Box.Bottom, maxy); + } + 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) + X = (int)minx, + Y = (int)miny, + Width = (int)(maxx - minx), + Height = (int)(maxy - miny) }; if (OptionalDataBits > 0) throw new ApplicationException("Optional bits in DCC data is not currently supported."); if ((CompressionFlags & 0x2) > 0) - EqualCellsBitstreamSize = bm.GetBits(20); + EqualCellsBitstreamSize = (int)bm.GetBits(20); - PixelMaskBitstreamSize = bm.GetBits(20); + PixelMaskBitstreamSize = (int)bm.GetBits(20); if ((CompressionFlags & 0x1) > 0) { - EncodingTypeBitsreamSize = bm.GetBits(20); - RawPixelCodesBitstreamSize = bm.GetBits(20); + EncodingTypeBitsreamSize = (int)bm.GetBits(20); + RawPixelCodesBitstreamSize = (int)bm.GetBits(20); } @@ -200,17 +211,22 @@ namespace OpenDiablo2.Common.Models for (var i = 0; i < 256; i++) paletteEntries.Add(bm.GetBit() != 0); - PaletteEntries = new int[paletteEntries.Count(x => x == true)]; + PaletteEntries = new byte[paletteEntries.Count(x => x == true)]; var paletteOffset = 0; for (var i = 0; i < 256; i++) { if (!paletteEntries[i]) continue; - PaletteEntries[paletteOffset++] = i; + PaletteEntries[paletteOffset++] = (byte)i; } + // HERE BE GIANTS: + // Because of the way this thing mashes bits together, BIT offset matters + // here. For example, if you are on byte offset 3, bit offset 6, and + // the EqualCellsBitstreamSize is 20 bytes, then the next bit stream + // will be located at byte 23, bit offset 6! var equalCellsBitstream = new BitMuncher(bm); bm.SkipBits(EqualCellsBitstreamSize); @@ -245,7 +261,6 @@ namespace OpenDiablo2.Common.Models if (rawPixelCodesBitstream.BitsRead != RawPixelCodesBitstreamSize) throw new ApplicationException("Did not read the correct number of bits!"); - bm.SkipBits(pixelCodeandDisplacement.BitsRead); } @@ -258,7 +273,7 @@ namespace OpenDiablo2.Common.Models for (var i = 0; i < DCC_MAX_PB_ENTRY; i++) PixelBuffer[i] = new PixelBufferEntry { Frame = -1, FrameCellIndex = -1, Value = new byte[4] }; - var cellBuffer = new PixelBufferEntry[Cells.Length]; + var cellBuffer = new PixelBufferEntry[HorizontalCellCount * VerticalCellCount]; var frameIndex = -1; var pbIndex = -1; @@ -274,16 +289,18 @@ namespace OpenDiablo2.Common.Models var currentCellY = cellY + originCellY; for (var cellX = 0; cellX < frame.HorizontalCellCount; cellX++, frameCellIndex++) { - var currentCell = (originCellX + cellX) + (currentCellY * frame.HorizontalCellCount); + var currentCell = (originCellX + cellX) + (currentCellY * HorizontalCellCount); var nextCell = false; var tmp = 0; if (cellBuffer[currentCell] != null) { if (EqualCellsBitstreamSize > 0) - tmp = ec.GetBit(); + tmp = (int)ec.GetBit(); + else + tmp = 0; if (tmp == 0) - pixelMask = (UInt32)pm.GetBits(4); + pixelMask = pm.GetBits(4); else nextCell = true; } @@ -296,25 +313,28 @@ namespace OpenDiablo2.Common.Models var pixelStack = new UInt32[4]; lastPixel = 0; int numberOfPixelBits = pixelMaskLookup[pixelMask]; - var encodingType = ((numberOfPixelBits != 0) && (EncodingTypeBitsreamSize > 0)) - ? et.GetBit() - : 0; + int encodingType = 0; + if ((numberOfPixelBits != 0) && (EncodingTypeBitsreamSize > 0)) + encodingType = (int)et.GetBit(); + else + encodingType = 0; + int decodedPixel = 0; for (int i = 0; i < numberOfPixelBits; i++) { if (encodingType != 0) { - pixelStack[i] = (UInt32)rp.GetBits(8); + pixelStack[i] = rp.GetBits(8); } else { pixelStack[i] = lastPixel; var pixelDisplacement = pcd.GetBits(4); - pixelStack[i] += (UInt32)pixelDisplacement; + pixelStack[i] += pixelDisplacement; while (pixelDisplacement == 15) { pixelDisplacement = pcd.GetBits(4); - pixelStack[i] += (UInt32)pixelDisplacement; + pixelStack[i] += pixelDisplacement; } } if (pixelStack[i] == lastPixel) @@ -349,10 +369,21 @@ namespace OpenDiablo2.Common.Models cellBuffer[currentCell] = newEntry; newEntry.Frame = frameIndex; - newEntry.FrameCellIndex = cellX + (cellY * HorizontalCellCount); + newEntry.FrameCellIndex = cellX + (cellY * frame.HorizontalCellCount); } } } + + + // Convert the palette entry index into actual palette entries + for (var i = 0; i < pbIndex; i++) + { + for (var x = 0; x < 4; x++) + { + var y = PixelBuffer[i].Value[x]; + PixelBuffer[i].Value[x] = PaletteEntries[y]; + } + } } private void CalculateCellOffsets() diff --git a/OpenDiablo2.Common/Models/Mobs/PlayerState.cs b/OpenDiablo2.Common/Models/Mobs/PlayerState.cs index cb6d8bdf..72121dfe 100644 --- a/OpenDiablo2.Common/Models/Mobs/PlayerState.cs +++ b/OpenDiablo2.Common/Models/Mobs/PlayerState.cs @@ -7,12 +7,16 @@ namespace OpenDiablo2.Common.Models.Mobs { public class PlayerState : MobState { + public Guid UID { get; protected set; } = Guid.NewGuid(); public eHero HeroType { get; protected set; } private IHeroTypeConfig HeroTypeConfig; private ILevelExperienceConfig ExperienceConfig; public int ClientHash { get; protected set; } public byte MovementDirection { get; set; } = 0; - public eMovementType MovementType { get; set; } = eMovementType.Stopped; + public eMovementType MovementType { get; set; } = eMovementType.Stopped; // TODO: This needs to mess with MobMode somehow + public eWeaponClass WeaponClass { get; set; } = eWeaponClass.HandToHand; // Temporary + public eArmorType ArmorType { get; set; } = eArmorType.Lite; // Temporary + public eMobMode MobMode { get; set; } = eMobMode.PlayerNeutral; // Temporary // Player character stats protected Stat Vitality; diff --git a/OpenDiablo2.Common/Models/PlayerInfo.cs b/OpenDiablo2.Common/Models/PlayerInfo.cs index 4196e0a1..013b8a23 100644 --- a/OpenDiablo2.Common/Models/PlayerInfo.cs +++ b/OpenDiablo2.Common/Models/PlayerInfo.cs @@ -8,8 +8,12 @@ namespace OpenDiablo2.Common.Models { public sealed class PlayerInfo { + public Guid UID { get; set; } public string Name { get; set; } public eHero Hero { get; set; } + public eWeaponClass WeaponClass { get; set; } + public eArmorType ArmorType { get; set; } + public eMobMode MobMode { get; set; } public PlayerLocationDetails LocationDetails { get; set; } public byte[] GetBytes() @@ -17,9 +21,13 @@ namespace OpenDiablo2.Common.Models var result = new List(); var nameBytes = Encoding.UTF8.GetBytes(Name); result.Add((byte)Hero); + result.Add((byte)WeaponClass); + result.Add((byte)ArmorType); + result.Add((byte)MobMode); result.AddRange(BitConverter.GetBytes((Int32)nameBytes.Length)); result.AddRange(nameBytes); result.AddRange(LocationDetails.GetBytes()); + result.AddRange(UID.ToByteArray()); return result.ToArray(); } @@ -27,9 +35,15 @@ namespace OpenDiablo2.Common.Models { var result = new PlayerInfo(); result.Hero = (eHero)data[offset]; - var nameLength = BitConverter.ToInt32(data, offset + 1); - result.Name = Encoding.UTF8.GetString(data, offset + 5, nameLength); - result.LocationDetails = PlayerLocationDetails.FromBytes(data, offset + 5 + nameLength); + result.WeaponClass= (eWeaponClass)data[offset + 1]; + result.ArmorType = (eArmorType)data[offset + 2]; + result.MobMode = (eMobMode)data[offset + 3]; + var nameLength = BitConverter.ToInt32(data, offset + 4); + result.Name = Encoding.UTF8.GetString(data, offset + 8, nameLength); + result.LocationDetails = PlayerLocationDetails.FromBytes(data, offset + 8 + nameLength); + var uidBytes = new byte[16]; + Array.Copy(data, offset + 8 + nameLength + PlayerLocationDetails.SizeInBytes + 1, uidBytes, 0, 16); + result.UID = new Guid(uidBytes); return result; } @@ -39,17 +53,22 @@ namespace OpenDiablo2.Common.Models public static class PlayerInfoExtensions { + // Map the player state to a PlayerInfo network package object. public static PlayerInfo ToPlayerInfo(this PlayerState source) => new PlayerInfo { + UID = source.UID, Hero = source.HeroType, LocationDetails = new PlayerLocationDetails { PlayerId = source.Id, PlayerX = source.GetPosition().X, - PlayerY = source.GetPosition().Y + PlayerY = source.GetPosition().Y, }, - Name = source.Name + Name = source.Name, + WeaponClass = source.WeaponClass, + ArmorType = source.ArmorType, + MobMode = source.MobMode }; } } diff --git a/OpenDiablo2.Common/OpenDiablo2.Common.csproj b/OpenDiablo2.Common/OpenDiablo2.Common.csproj index 355d520c..9f0db12d 100644 --- a/OpenDiablo2.Common/OpenDiablo2.Common.csproj +++ b/OpenDiablo2.Common/OpenDiablo2.Common.csproj @@ -78,6 +78,7 @@ + diff --git a/OpenDiablo2.Core/AutofacModule.cs b/OpenDiablo2.Core/AutofacModule.cs index 1a12d811..ae171bf5 100644 --- a/OpenDiablo2.Core/AutofacModule.cs +++ b/OpenDiablo2.Core/AutofacModule.cs @@ -1,5 +1,6 @@ using Autofac; using OpenDiablo2.Common.Interfaces; +using OpenDiablo2.Common.Interfaces.Drawing; using OpenDiablo2.Common.Interfaces.Mobs; using OpenDiablo2.Core.GameState_; using OpenDiablo2.Core.Map_Engine; @@ -30,7 +31,6 @@ namespace OpenDiablo2.Core builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().SingleInstance(); // TODO: This needs to have client and server versions... } } diff --git a/OpenDiablo2.Core/MPQProvider.cs b/OpenDiablo2.Core/MPQProvider.cs index 6a191d05..9080e937 100644 --- a/OpenDiablo2.Core/MPQProvider.cs +++ b/OpenDiablo2.Core/MPQProvider.cs @@ -59,7 +59,12 @@ namespace OpenDiablo2.Core public IEnumerable GetMPQs() => mpqs; public Stream GetStream(string fileName) - => mpqs[mpqLookup[fileName.ToLower()]].OpenFile(fileName); + { + if (!mpqLookup.ContainsKey(fileName.ToLower())) + return null; + + return mpqs[mpqLookup[fileName.ToLower()]].OpenFile(fileName); + } public IEnumerable GetTextFile(string fileName) diff --git a/OpenDiablo2.Core/Map Engine/MapEngine.cs b/OpenDiablo2.Core/Map Engine/MapEngine.cs index 57ef8e4b..aed80680 100644 --- a/OpenDiablo2.Core/Map Engine/MapEngine.cs +++ b/OpenDiablo2.Core/Map Engine/MapEngine.cs @@ -1,9 +1,12 @@ using System; +using System.Collections.Generic; using System.Drawing; using System.Linq; using OpenDiablo2.Common; using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Interfaces; +using OpenDiablo2.Common.Interfaces.Drawing; +using OpenDiablo2.Common.Models; namespace OpenDiablo2.Core.Map_Engine { @@ -12,6 +15,9 @@ namespace OpenDiablo2.Core.Map_Engine private readonly IGameState gameState; private readonly IRenderWindow renderWindow; private readonly IResourceManager resourceManager; + private readonly ISessionManager sessionManager; + + private List characterRenderers = new List(); public int FocusedPlayerId { get; set; } = 0; @@ -32,7 +38,6 @@ namespace OpenDiablo2.Core.Map_Engine private ISprite loadingSprite; private int cOffX, cOffY; - //private ISprite[] tempMapCell; private const int cellSizeX = 160, @@ -43,14 +48,63 @@ namespace OpenDiablo2.Core.Map_Engine public MapEngine( IGameState gameState, IRenderWindow renderWindow, - IResourceManager resourceManager + IResourceManager resourceManager, + ISessionManager sessionManager ) { this.gameState = gameState; this.renderWindow = renderWindow; this.resourceManager = resourceManager; + this.sessionManager = sessionManager; loadingSprite = renderWindow.LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading, new Point(300, 400)); + sessionManager.OnPlayerInfo += OnPlayerInfo; + sessionManager.OnLocatePlayers += OnLocatePlayers; + } + + private void OnLocatePlayers(int clientHash, IEnumerable playerLocationDetails) + { + foreach(var loc in playerLocationDetails) + { + var cr = characterRenderers.FirstOrDefault(x => x.LocationDetails.PlayerId == loc.PlayerId); + cr.LocationDetails = loc; + } + } + + 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)); + + // Update existing character renderers + foreach (var cr in characterRenderers) + { + var info = playerInfo.FirstOrDefault(x => x.UID == cr.UID); + if (info == null) + continue; + + // TODO: This shouldn't be necessary... + cr.LocationDetails = info.LocationDetails; + cr.MobMode = info.MobMode; + cr.WeaponClass = info.WeaponClass; + cr.Hero = info.Hero; + cr.ArmorType = info.ArmorType; + + } + + // Add character renderers for characters that now exist + foreach(var info in playerInfo.Where(x => !characterRenderers.Any(z => x.UID == z.UID))) + { + var cr = renderWindow.CreateCharacterRenderer(); + cr.UID = info.UID; + cr.LocationDetails = info.LocationDetails; + cr.MobMode = info.MobMode; + cr.WeaponClass = info.WeaponClass; + cr.Hero = info.Hero; + cr.ArmorType = info.ArmorType; + characterRenderers.Add(cr); + cr.ResetAnimationData(); + } } public void Render() @@ -77,9 +131,15 @@ namespace OpenDiablo2.Core.Map_Engine foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.Floor)) renderWindow.DrawMapCell(cellInfo, 320 + (int)px + (int)ox + xOffset, 210 + (int)py + (int)oy); + foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.WallLower)) renderWindow.DrawMapCell(cellInfo, 320 + (int)px + (int)ox + xOffset, 210 + (int)py + (int)oy); + // TODO: We need to render the characters infront of, or behind the wall properly... + foreach (var character in characterRenderers.Where(x => Math.Truncate(x.LocationDetails.PlayerX) == ax && Math.Truncate(x.LocationDetails.PlayerY) == ay)) + character.Render(320 + (int)px + (int)ox + xOffset, 210 + (int)py + (int)oy); + + foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.WallUpper)) renderWindow.DrawMapCell(cellInfo, 320 + (int)px + (int)ox + xOffset, 210 + (int)py + (int)oy); @@ -94,6 +154,9 @@ namespace OpenDiablo2.Core.Map_Engine public void Update(long ms) { + foreach (var character in characterRenderers) + character.Update(ms); + if (FocusedPlayerId != 0) { var player = gameState.PlayerInfos.FirstOrDefault(x => x.LocationDetails.PlayerId == FocusedPlayerId); diff --git a/OpenDiablo2.Core/ResourceManager.cs b/OpenDiablo2.Core/ResourceManager.cs index 5a65d733..12b39cfb 100644 --- a/OpenDiablo2.Core/ResourceManager.cs +++ b/OpenDiablo2.Core/ResourceManager.cs @@ -93,9 +93,14 @@ namespace OpenDiablo2.Core public MPQDCC GetPlayerDCC(MPQCOF.COFLayer cofLayer, eArmorType armorType, Palette palette) { + // TODO: We need to cache this... byte[] binaryData; + using (var stream = mpqProvider.GetStream(cofLayer.GetDCCPath(armorType))) { + if (stream == null) + return null; + binaryData = new byte[stream.Length]; stream.Read(binaryData, 0, (int)stream.Length); } diff --git a/OpenDiablo2.SDL2/AutofacModule.cs b/OpenDiablo2.SDL2/AutofacModule.cs index 175f955c..49498474 100644 --- a/OpenDiablo2.SDL2/AutofacModule.cs +++ b/OpenDiablo2.SDL2/AutofacModule.cs @@ -1,5 +1,6 @@ using Autofac; using OpenDiablo2.Common.Interfaces; +using OpenDiablo2.Common.Interfaces.Drawing; namespace OpenDiablo2.SDL2_ { @@ -13,7 +14,6 @@ namespace OpenDiablo2.SDL2_ builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().SingleInstance(); - } } } diff --git a/OpenDiablo2.SDL2/OpenDiablo2.SDL2.csproj b/OpenDiablo2.SDL2/OpenDiablo2.SDL2.csproj index ad0078a0..f0fe463a 100644 --- a/OpenDiablo2.SDL2/OpenDiablo2.SDL2.csproj +++ b/OpenDiablo2.SDL2/OpenDiablo2.SDL2.csproj @@ -52,6 +52,7 @@ + diff --git a/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs b/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs new file mode 100644 index 00000000..c3afa382 --- /dev/null +++ b/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs @@ -0,0 +1,106 @@ +using System; +using System.Linq; +using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Interfaces; +using OpenDiablo2.Common.Interfaces.Drawing; +using OpenDiablo2.Common.Models; +using SDL2; + +namespace OpenDiablo2.SDL2_ +{ + public sealed class SDL2CharacterRenderer : ICharacterRenderer + { + static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + public Guid UID { get; set; } + public PlayerLocationDetails LocationDetails { get; set; } + public eHero Hero { get; set; } + public eWeaponClass WeaponClass { get; set; } + public eArmorType ArmorType { get; set; } + public eMobMode MobMode { get; set; } + + private IntPtr renderer; + private IntPtr tempTexture; + + private readonly IResourceManager resourceManager; + private readonly IPaletteProvider paletteProvider; + + private MPQCOF animationData; + private MPQDCC[] layerData; + + public SDL2CharacterRenderer(IntPtr renderer, IResourceManager resourceManager, IPaletteProvider paletteProvider) + { + this.resourceManager = resourceManager; + this.paletteProvider = paletteProvider; + this.renderer = renderer; + } + + public void Render(int pixelOffsetX, int pixelOffsetY) + { + + } + + public void Update(long ms) + { + + } + + public void Dispose() + { + + } + + public void ResetAnimationData() + { + + animationData = resourceManager.GetPlayerAnimation(Hero, WeaponClass, MobMode); + if (animationData == null) + throw new ApplicationException("Could not locate animation for the character!"); + + var palette = paletteProvider.PaletteTable["Units"]; + var data = animationData.Layers + .Select(layer => resourceManager.GetPlayerDCC(layer, ArmorType, palette)) + .ToArray(); + + log.Warn($"{data.Where(x => x == null).Count()} animation layers were not found!"); + + layerData = data.Where(x => x != null) + .ToArray(); + + CacheFrames(); + } + + private unsafe void CacheFrames() + { + var dirIndex = 0; // TODO: Specify the real direction + var frameIndex = 0; + + var dirAnimation = animationData.Animations[dirIndex]; + var framesToAnimate = dirAnimation.FramesPerDirection; + + foreach (var layer in layerData) + { + var direction = layer.Directions[dirIndex]; + var frame = direction.Frames[0]; + var texture = SDL.SDL_CreateTexture( + renderer, + SDL.SDL_PIXELFORMAT_ARGB8888, + (int)SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, + frame.Width, + frame.Height + ); + + + IntPtr pixels; + int pitch; + + SDL.SDL_LockTexture(texture, IntPtr.Zero, out pixels, out pitch); + + SDL.SDL_UnlockTexture(texture); + + + tempTexture = texture; + } + } + } +} diff --git a/OpenDiablo2.SDL2/SDL2RenderWindow.cs b/OpenDiablo2.SDL2/SDL2RenderWindow.cs index ef688b3c..eb6730ae 100644 --- a/OpenDiablo2.SDL2/SDL2RenderWindow.cs +++ b/OpenDiablo2.SDL2/SDL2RenderWindow.cs @@ -3,6 +3,7 @@ using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using OpenDiablo2.Common.Interfaces; +using OpenDiablo2.Common.Interfaces.Drawing; using OpenDiablo2.Common.Models; using SDL2; @@ -470,5 +471,7 @@ namespace OpenDiablo2.SDL2_ public uint GetTicks() => SDL.SDL_GetTicks(); + public ICharacterRenderer CreateCharacterRenderer() + => new SDL2CharacterRenderer(this.renderer, resourceManager, paletteProvider); } } diff --git a/OpenDiablo2.Scenes/Game.cs b/OpenDiablo2.Scenes/Game.cs index 96853ede..7538bfc0 100644 --- a/OpenDiablo2.Scenes/Game.cs +++ b/OpenDiablo2.Scenes/Game.cs @@ -1,6 +1,5 @@ using System; using System.Drawing; -using System.Numerics; using OpenDiablo2.Common; using OpenDiablo2.Common.Attributes; using OpenDiablo2.Common.Enums; @@ -78,11 +77,6 @@ namespace OpenDiablo2.Scenes menuButton = createButton(eButtonType.Menu); menuButton.Location = new Point(393, 561); menuButton.OnToggle = OnMenuToggle; - - /*var item = itemManager.getItem("hdm"); - var cursorsprite = renderWindow.LoadSprite(ResourcePaths.GeneratePathForItem(item.InvFile), Palettes.Units); - - renderWindow.MouseCursor = renderWindow.LoadCursor(cursorsprite, 0, new Point(cursorsprite.FrameSize.Width/2, cursorsprite.FrameSize.Height / 2));*/ } private void OnMenuToggle(bool isToggled) @@ -216,4 +210,4 @@ namespace OpenDiablo2.Scenes } } -} +} diff --git a/OpenDiablo2.Scenes/MainMenu.cs b/OpenDiablo2.Scenes/MainMenu.cs index 1648317b..3072ade0 100644 --- a/OpenDiablo2.Scenes/MainMenu.cs +++ b/OpenDiablo2.Scenes/MainMenu.cs @@ -79,11 +79,6 @@ 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" }; for (int i = 0; i < scenesToLoad.Count(); i++) @@ -94,11 +89,9 @@ namespace OpenDiablo2.Scenes getScene(scenesToLoad[i]); } - /* musicProvider.LoadSong(mpqProvider.GetStream("data\\global\\music\\introedit.wav")); - musicProvider.PlaySong(); */ }