1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-20 06:05:23 +00:00

Added animations. Added walking/running.

This commit is contained in:
Tim Sarbin 2018-12-08 01:01:30 -05:00
parent 0b545fb876
commit 9f078a8ace
10 changed files with 301 additions and 53 deletions

View File

@ -8,19 +8,26 @@ namespace OpenDiablo2.Common.Models
{
public sealed class MPQDCC
{
public class PixelBufferEntry
public sealed class PixelBufferEntry
{
public byte[] Value;
public int Frame;
public int FrameCellIndex;
}
public struct Cell
public sealed class Cell
{
public int Width;
public int Height;
public int XOffset;
public int YOffset;
public int LastWidth;
public int LastHeight;
public int LastXOffset;
public int LastYOffset;
public byte[] PixelData;
}
public sealed class MPQDCCDirectionFrame
@ -59,7 +66,7 @@ namespace OpenDiablo2.Common.Models
}
public void MakeCells(MPQDCCDirection direction)
public void CalculateCells(MPQDCCDirection direction)
{
var w = 4 - ((Box.Left - direction.Box.Left) % 4); // Width of the first column (in pixels)
if ((Width - w) <= 1)
@ -131,7 +138,6 @@ namespace OpenDiablo2.Common.Models
}
public sealed class MPQDCCDirection
{
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; }
@ -166,14 +172,14 @@ namespace OpenDiablo2.Common.Models
OptionalDataBits = crazyBitTable[bm.GetBits(4)];
CodedBytesBits = crazyBitTable[bm.GetBits(4)];
Frames = new MPQDCCDirectionFrame[file.NumberOfFrames];
Frames = new MPQDCCDirectionFrame[file.FramesPerDirection];
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++)
for (var frameIdx = 0; frameIdx < file.FramesPerDirection; frameIdx++)
{
Frames[frameIdx] = new MPQDCCDirectionFrame(bm, this);
@ -242,13 +248,20 @@ namespace OpenDiablo2.Common.Models
var pixelCodeandDisplacement = new BitMuncher(bm);
CalculateCellOffsets();
// Calculate the cells for the direction
CaculateCells();
// Caculate the cells for each of the frames
foreach (var frame in Frames)
frame.MakeCells(this);
frame.CalculateCells(this);
// Fill in the pixel buffer
FillPixelBuffer(pixelCodeandDisplacement, equalCellsBitstream, pixelMaskBitstream, encodingTypeBitsream, rawPixelCodesBitstream);
// Generate the actual frame pixel data
GenerateFrames(file, pixelCodeandDisplacement);
// Verify that everything we expected to read was actually read (sanity check)...
if (equalCellsBitstream.BitsRead != EqualCellsBitstreamSize)
throw new ApplicationException("Did not read the correct number of bits!");
@ -264,13 +277,103 @@ namespace OpenDiablo2.Common.Models
bm.SkipBits(pixelCodeandDisplacement.BitsRead);
}
private void GenerateFrames(MPQDCC file, BitMuncher pcd)
{
var pbIdx = 0;
foreach (var cell in Cells)
{
cell.LastWidth = -1;
cell.LastHeight = -1;
cell.PixelData = new byte[cell.Width * cell.Height];
}
var frameIndex = -1;
foreach (var frame in Frames)
{
frameIndex++;
var numberOfCells = frame.HorizontalCellCount * frame.VerticalCellCount;
var c = -1;
foreach (var cell in frame.Cells)
{
c++;
var cellX = cell.XOffset / 4;
var cellY = cell.YOffset / 4;
var cellIndex = cellX + (cellY * HorizontalCellCount);
var bufferCell = Cells[cellIndex];
var pbe = PixelBuffer[pbIdx];
if ((pbe.Frame != frameIndex) || (pbe.FrameCellIndex != c))
{
// This buffer cell has an EqualCell bit set to 1, so copy the frame cell or clear it
if ((cell.Width != bufferCell.LastWidth) || (cell.Height != bufferCell.LastHeight))
{
// Different sizes
/// TODO: Clear the pixels of the frame cell
for (var i = 0; i < bufferCell.PixelData.Length; i++)
bufferCell.PixelData[i] = 0x00;
}
else
{
// Same sizes
// Copy the old frame cell into the new position
// blit(dir->bmp, dir->bmp, buff_cell->last_x0, buff_cell->last_y0, cell->x0, cell->y0, cell->w, cell->h );
for (var i = 0; i < bufferCell.PixelData.Length; i++)
bufferCell.PixelData[i] = cell.PixelData[i];
bufferCell.LastWidth = cell.LastWidth;
bufferCell.LastHeight = cell.LastHeight;
// Copy it again into the final frame image
// blit(cell->bmp, frm_bmp, 0, 0, cell->x0, cell->y0, cell->w, cell->h );
}
}
else
{
if (pbe.Value[0] == pbe.Value[1])
{
// Clear the frame
}
else
{
// Fill the frame cell with the pixels
var bitsToRead = (pbe.Value[1] == pbe.Value[2]) ? 1 : 2;
cell.PixelData = new byte[cell.Width * cell.Height];
for (var y = 0; y < cell.Height; y++)
{
for (var x = 0; x < cell.Width; x++)
{
var paletteIndex = pcd.GetBits(bitsToRead);
cell.PixelData[x + (y * cell.Width)] = pbe.Value[paletteIndex];
}
}
}
// Copy the frame cell into the frame
//blit(cell->bmp, frm_bmp, 0, 0, cell->x0, cell->y0, cell->w, cell->h );
pbIdx++;
}
}
}
}
private static readonly int[] pixelMaskLookup = new int[] { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
private void FillPixelBuffer(BitMuncher pcd, BitMuncher ec, BitMuncher pm, BitMuncher et, BitMuncher rp)
{
UInt32 lastPixel = 0;
PixelBuffer = new PixelBufferEntry[DCC_MAX_PB_ENTRY];
for (var i = 0; i < DCC_MAX_PB_ENTRY; i++)
var maxCellX = Frames.Sum(x => x.HorizontalCellCount);
var maxCellY = Frames.Sum(x => x.VerticalCellCount);
PixelBuffer = new PixelBufferEntry[maxCellX * maxCellY];
for (var i = 0; i < maxCellX * maxCellY; i++)
PixelBuffer[i] = new PixelBufferEntry { Frame = -1, FrameCellIndex = -1, Value = new byte[4] };
var cellBuffer = new PixelBufferEntry[HorizontalCellCount * VerticalCellCount];
@ -353,7 +456,7 @@ namespace OpenDiablo2.Common.Models
pbIndex++;
var newEntry = PixelBuffer[pbIndex];
var curIdx = decodedPixel - 1;
for (int i = 0; i < 4; i++)
{
if ((pixelMask & (1 << i)) != 0)
@ -386,7 +489,7 @@ namespace OpenDiablo2.Common.Models
}
}
private void CalculateCellOffsets()
private void CaculateCells()
{
// Calculate the number of vertical and horizontal cells we need
HorizontalCellCount = 1 + (Box.Width - 1) / 4;
@ -441,7 +544,7 @@ namespace OpenDiablo2.Common.Models
public int Signature { get; private set; }
public int Version { get; private set; }
public int NumberOfDirections { get; private set; }
public int NumberOfFrames { get; private set; }
public int FramesPerDirection { get; private set; }
public MPQDCCDirection[] Directions { get; private set; }
public MPQDCC(byte[] data, Palette palette)
@ -453,7 +556,7 @@ namespace OpenDiablo2.Common.Models
Version = bm.GetByte();
NumberOfDirections = bm.GetByte();
NumberOfFrames = bm.GetInt32();
FramesPerDirection = bm.GetInt32();
if (bm.GetInt32() != 1)
throw new ApplicationException("This value isn't 1. It has to be 1.");

View File

@ -16,7 +16,7 @@ namespace OpenDiablo2.Common.Models.Mobs
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
public eMobMode MobMode { get; set; } = eMobMode.PlayerTownWalk; // Temporary
// Player character stats
protected Stat Vitality;

View File

@ -42,12 +42,12 @@ namespace OpenDiablo2.Common.Models
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);
Array.Copy(data, offset + 8 + nameLength + PlayerLocationDetails.SizeInBytes, uidBytes, 0, 16);
result.UID = new Guid(uidBytes);
return result;
}
public int SizeInBytes => 5 + Encoding.UTF8.GetByteCount(Name) + PlayerLocationDetails.SizeInBytes;
public int SizeInBytes => 8 + Encoding.UTF8.GetByteCount(Name) + PlayerLocationDetails.SizeInBytes + 16;
}

View File

@ -12,6 +12,7 @@ namespace OpenDiablo2.Common.Models
public float PlayerY { get; set; }
public eMovementType MovementType { get; set; }
public int MovementDirection { get; set; }
public float MovementSpeed { get; set; }
// TODO: They may not be on the same 'anchor map'...
public byte[] GetBytes()
@ -22,32 +23,40 @@ namespace OpenDiablo2.Common.Models
result.AddRange(BitConverter.GetBytes((float)PlayerY));
result.AddRange(BitConverter.GetBytes((Int32)MovementDirection));
result.AddRange(BitConverter.GetBytes((byte)MovementType));
result.AddRange(BitConverter.GetBytes((float)MovementSpeed));
return result.ToArray();
}
public static PlayerLocationDetails FromBytes(byte[] data, int offset = 0)
=> new PlayerLocationDetails
{
var result = new PlayerLocationDetails
{
PlayerId = BitConverter.ToInt32(data, offset + 0),
PlayerX = BitConverter.ToSingle(data, offset + 4),
PlayerY = BitConverter.ToSingle(data, offset + 8),
MovementDirection = BitConverter.ToInt32(data, offset + 12),
MovementType = (eMovementType)data[offset + 16]
MovementType = (eMovementType)data[offset + 16],
MovementSpeed = BitConverter.ToSingle(data, offset + 18)
};
public static int SizeInBytes => 17;
return result;
}
public static int SizeInBytes => 22;
}
public static class PlayerLocationDetailsExtensions
{
public static PlayerLocationDetails ToPlayerLocationDetails(this PlayerState source)
=> new PlayerLocationDetails
{
var result = new PlayerLocationDetails
{
PlayerId = source.Id,
PlayerX = source.GetPosition().X,
PlayerY = source.GetPosition().Y,
MovementType = source.MovementType,
MovementDirection = source.MovementDirection
MovementDirection = source.MovementDirection,
MovementSpeed = (float)(source.MovementType == eMovementType.Running ? source.GetRunVelocity() : source.GetWalkVeloicty()) / 4f
};
return result;
}
}
}

View File

@ -434,8 +434,8 @@ namespace OpenDiablo2.Core.GameState_
var rads = (float)player.LocationDetails.MovementDirection * 22 * (float)Deg2Rad;
var moveX = (float)Math.Cos(rads) * seconds * 2f;
var moveY = (float)Math.Sin(rads) * seconds * 2f;
var moveX = (float)Math.Cos(rads) * seconds * player.LocationDetails.MovementSpeed;
var moveY = (float)Math.Sin(rads) * seconds * player.LocationDetails.MovementSpeed;
player.LocationDetails.PlayerX += moveX;
player.LocationDetails.PlayerY += moveY;

View File

@ -67,7 +67,11 @@ namespace OpenDiablo2.Core.Map_Engine
foreach(var loc in playerLocationDetails)
{
var cr = characterRenderers.FirstOrDefault(x => x.LocationDetails.PlayerId == loc.PlayerId);
var newDirection = loc.MovementDirection != cr.LocationDetails.MovementDirection;
var stanceChanged = loc.MovementType != cr.LocationDetails.MovementType;
cr.LocationDetails = loc;
if (newDirection || stanceChanged)
cr.ResetAnimationData();
}
}
@ -135,20 +139,27 @@ namespace OpenDiablo2.Core.Map_Engine
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);
// TODO: We need to render the characters infront of, or behind the wall properly...
if (ty == 1 && tx == 1)
{
foreach (var character in characterRenderers/*.Where(x => Math.Truncate(x.LocationDetails.PlayerX) == ax && Math.Truncate(x.LocationDetails.PlayerY) == ay)*/)
{
// TODO: Temporary hack
character.Render(400, 280);
}
}
foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.Roof))
renderWindow.DrawMapCell(cellInfo, 320 + (int)px + (int)ox + xOffset, 210 + (int)py + (int)oy);
}
}
}

View File

@ -84,8 +84,10 @@ namespace OpenDiablo2.GameServer_
var rads = (float)player.MovementDirection * 22 * (float)Deg2Rad;
var moveX = (float)Math.Cos(rads) * seconds * 2f;
var moveY = (float)Math.Sin(rads) * seconds * 2f;
var speed = (float)(player.MovementType == eMovementType.Running ? player.GetRunVelocity() : player.GetWalkVeloicty()) / 4f;
var moveX = (float)Math.Cos(rads) * seconds * speed;
var moveY = (float)Math.Sin(rads) * seconds * speed;
player.X += moveX;
player.Y += moveY;

View File

@ -1,5 +1,8 @@
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;
@ -10,6 +13,18 @@ namespace OpenDiablo2.SDL2_
{
public sealed class SDL2CharacterRenderer : ICharacterRenderer
{
sealed class DirectionCacheItem
{
public int Direction { get; set; }
public eMobMode MobMode { get; set; }
public SDL.SDL_Rect[] SpriteRect { get; set; }
public IntPtr[] SpriteTexture { get; set; }
public int FramesToAnimate { get; set; }
public int AnimationSpeed { get; set; }
public int RenderFrameIndex { get; set; }
}
static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public Guid UID { get; set; }
@ -20,7 +35,10 @@ namespace OpenDiablo2.SDL2_
public eMobMode MobMode { get; set; }
private IntPtr renderer;
private IntPtr tempTexture;
private List<DirectionCacheItem> directionCache = new List<DirectionCacheItem>();
DirectionCacheItem currentDirectionCache;
private float seconds;
private readonly IResourceManager resourceManager;
private readonly IPaletteProvider paletteProvider;
@ -28,6 +46,8 @@ namespace OpenDiablo2.SDL2_
private MPQCOF animationData;
private MPQDCC[] layerData;
static readonly byte[] directionConversion = new byte[] { 3, 15, 4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14 };
public SDL2CharacterRenderer(IntPtr renderer, IResourceManager resourceManager, IPaletteProvider paletteProvider)
{
this.resourceManager = resourceManager;
@ -37,12 +57,34 @@ namespace OpenDiablo2.SDL2_
public void Render(int pixelOffsetX, int pixelOffsetY)
{
if (currentDirectionCache == null)
return;
var destRect = new SDL.SDL_Rect
{
x = pixelOffsetX + currentDirectionCache.SpriteRect[currentDirectionCache.RenderFrameIndex].x,
y = pixelOffsetY + currentDirectionCache.SpriteRect[currentDirectionCache.RenderFrameIndex].y,
w = currentDirectionCache.SpriteRect[currentDirectionCache.RenderFrameIndex].w,
h = currentDirectionCache.SpriteRect[currentDirectionCache.RenderFrameIndex].h
};
SDL.SDL_RenderCopy(renderer, currentDirectionCache.SpriteTexture[currentDirectionCache.RenderFrameIndex], IntPtr.Zero, ref destRect);
}
public void Update(long ms)
{
if (currentDirectionCache == null)
return;
seconds += ((float)ms / 1000f);
var animationSeg = (15f / (float)currentDirectionCache.AnimationSpeed);
while (seconds >= animationSeg)
{
seconds -= animationSeg;
currentDirectionCache.RenderFrameIndex++;
if (currentDirectionCache.RenderFrameIndex >= currentDirectionCache.FramesToAnimate)
currentDirectionCache.RenderFrameIndex = 0;
}
}
public void Dispose()
@ -52,6 +94,29 @@ namespace OpenDiablo2.SDL2_
public void ResetAnimationData()
{
switch (LocationDetails.MovementType)
{
case eMovementType.Stopped:
MobMode = eMobMode.PlayerTownNeutral;
break;
case eMovementType.Walking:
MobMode = eMobMode.PlayerTownWalk;
break;
case eMovementType.Running:
MobMode = eMobMode.PlayerRun;
break;
default:
MobMode = eMobMode.PlayerNeutral;
break;
}
currentDirectionCache = directionCache.FirstOrDefault(x => x.MobMode == MobMode && x.Direction == directionConversion[LocationDetails.MovementDirection]);
if (currentDirectionCache != null)
{
currentDirectionCache.RenderFrameIndex = 0;
seconds = 0f;
return;
}
animationData = resourceManager.GetPlayerAnimation(Hero, WeaponClass, MobMode);
if (animationData == null)
@ -72,35 +137,92 @@ namespace OpenDiablo2.SDL2_
private unsafe void CacheFrames()
{
var dirIndex = 0; // TODO: Specify the real direction
var frameIndex = 0;
var cache = new DirectionCacheItem
{
MobMode = MobMode,
Direction = directionConversion[LocationDetails.MovementDirection]
};
var dirAnimation = animationData.Animations[dirIndex];
var framesToAnimate = dirAnimation.FramesPerDirection;
var palette = paletteProvider.PaletteTable[Palettes.Units];
var dirAnimation = animationData.Animations[0];
cache.FramesToAnimate = dirAnimation.FramesPerDirection;
cache.AnimationSpeed = dirAnimation.AnimationSpeed;
cache.RenderFrameIndex = 0;
var minX = Int32.MaxValue;
var minY = Int32.MaxValue;
var maxX = Int32.MinValue;
var maxY = Int32.MinValue;
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
);
minX = Math.Min(minX, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Left);
minY = Math.Min(minY, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Top);
maxX = Math.Max(maxX, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Right);
maxY = Math.Max(maxY, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Bottom);
}
var frameW = (maxX - minX) * 2; // Hack
var frameH = (maxY - minY) * 2;
cache.SpriteTexture = new IntPtr[cache.FramesToAnimate];
cache.SpriteRect = new SDL.SDL_Rect[cache.FramesToAnimate];
for (var frameIndex = 0; frameIndex < cache.FramesToAnimate; frameIndex++)
{
var texture = SDL.SDL_CreateTexture(renderer, SDL.SDL_PIXELFORMAT_ARGB8888, (int)SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, frameW, frameH);
IntPtr pixels;
int pitch;
SDL.SDL_LockTexture(texture, IntPtr.Zero, out pixels, out pitch);
UInt32* data = (UInt32*)pixels;
foreach (var layer in layerData)
{
var direction = layer.Directions[directionConversion[LocationDetails.MovementDirection]];
var frame = direction.Frames[frameIndex];
foreach (var cell in frame.Cells)
{
if (cell.PixelData == null)
continue; // TODO: This isn't good
for (int y = 0; y < cell.Height; y++)
{
for (int x = 0; x < cell.Width; x++)
{
// Index 0 is always transparent
var paletteIndex = cell.PixelData[x + (y * cell.Width)];
if (paletteIndex == 0)
continue;
var color = palette.Colors[paletteIndex];
var relativeX = (frame.XOffset - minX);
var relativeY = (frame.YOffset - minY);
var offsetX = x + cell.XOffset + (frame.Box.X - minX);
var offsetY = y + cell.YOffset + (frame.Box.Y - minY);
if (offsetX < 0 || offsetX > frameW || offsetY < 0 || offsetY > frameH)
throw new ApplicationException("There is nothing we can do now.");
data[offsetX + (offsetY * (pitch / 4))] = color;
}
}
}
}
SDL.SDL_UnlockTexture(texture);
SDL.SDL_SetTextureBlendMode(texture, SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND);
// TODO: Temporary code!
cache.SpriteTexture[frameIndex] = texture;
cache.SpriteRect[frameIndex] = new SDL.SDL_Rect { x = minX, y = minY, w = frameW, h = frameH };
tempTexture = texture;
directionCache.Add(cache);
currentDirectionCache = cache;
}
}
}
}

View File

@ -153,6 +153,8 @@ namespace OpenDiablo2.Scenes
if (cursorDirection < 0)
cursorDirection += 360;
var actualDirection = (byte)(cursorDirection / 22);
if (actualDirection >= 16)
actualDirection -= 16;
if (mouseInfoProvider.LeftMouseDown && (lastMovementType == eMovementType.Stopped || lastDirection != actualDirection))
{

View File

@ -110,16 +110,15 @@ namespace OpenDiablo2.ServiceBus
private void OnMovementRequestHandler(int clientHash, byte direction, eMovementType movementType)
{
// TODO: Actually move the player ....
var player = gameServer.Players.FirstOrDefault(x => x.ClientHash == clientHash);
if (player == null)
return;
// TODO: The server needs to actually manage player movement...
player.MovementDirection = direction;
player.MovementType = movementType;
player.MovementDirection = direction;
Send(new MFLocatePlayers(gameServer.Players.Select(x => x.ToPlayerLocationDetails())));
}