1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-20 22:25:24 +00:00

Fixed animation data issue. Started character renderer.

This commit is contained in:
Tim Sarbin 2018-12-07 18:45:54 -05:00
parent 2f1f15c7b5
commit 3902c9d9c0
18 changed files with 325 additions and 65 deletions

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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()

View File

@ -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;

View File

@ -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<byte>();
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
};
}
}

View File

@ -78,6 +78,7 @@
<Compile Include="Enums\Mobs\eDamageTypes.cs" />
<Compile Include="Enums\Mobs\eMobFlags.cs" />
<Compile Include="Enums\Mobs\eStatModifierType.cs" />
<Compile Include="Interfaces\Drawing\ICharacterRenderer.cs" />
<Compile Include="Interfaces\IItemManager.cs" />
<Compile Include="Extensions\MobManagerExtensions.cs" />
<Compile Include="Interfaces\IGameServer.cs" />

View File

@ -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<ResourceManager>().As<IResourceManager>().SingleInstance();
builder.RegisterType<TextDictionary>().As<ITextDictionary>().SingleInstance();
builder.RegisterType<TextBox>().As<ITextBox>().InstancePerDependency();
builder.RegisterType<MobManager>().As<IMobManager>().SingleInstance(); // TODO: This needs to have client and server versions...
}
}

View File

@ -59,7 +59,12 @@ namespace OpenDiablo2.Core
public IEnumerable<MPQ> 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<string> GetTextFile(string fileName)

View File

@ -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<ICharacterRenderer> characterRenderers = new List<ICharacterRenderer>();
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> 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> 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);

View File

@ -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);
}

View File

@ -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<SDL2RenderWindow>().AsImplementedInterfaces().SingleInstance();
builder.RegisterType<SDL2MusicPlayer>().AsImplementedInterfaces().SingleInstance();
}
}
}

View File

@ -52,6 +52,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AutofacModule.cs" />
<Compile Include="SDL2CharacterRenderer.cs" />
<Compile Include="CS-SDL\SDL2.cs" />
<Compile Include="CS-SDL\SDL2_image.cs" />
<Compile Include="CS-SDL\SDL2_mixer.cs" />

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}

View File

@ -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
}
}
}
}

View File

@ -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();
*/
}