diff --git a/OpenDiablo2.Common/Models/ImageSet.cs b/OpenDiablo2.Common/Models/ImageSet.cs index c1be91c4..76ade1c4 100644 --- a/OpenDiablo2.Common/Models/ImageSet.cs +++ b/OpenDiablo2.Common/Models/ImageSet.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; namespace OpenDiablo2.Common.Models { - public sealed class ImageFrame : IDisposable + public sealed class ImageFrame { public UInt32 Flip { get; internal set; } public UInt32 Width { get; internal set; } @@ -18,7 +18,8 @@ namespace OpenDiablo2.Common.Models public UInt32 Length { get; internal set; } public Int16[] ImageData { get; internal set; } - public void Dispose() + + ~ImageFrame() { ImageData = null; } @@ -31,7 +32,7 @@ namespace OpenDiablo2.Common.Models } } - public sealed class ImageSet : IDisposable + public sealed class ImageSet { private UInt32[] framePointers; public ImageFrame[] Frames { get; private set; } @@ -117,8 +118,10 @@ namespace OpenDiablo2.Common.Models return result; } - public void Dispose() + ~ImageSet() { + framePointers = null; + Frames = Array.Empty(); } } } diff --git a/OpenDiablo2.Common/Models/MPQCOF.cs b/OpenDiablo2.Common/Models/MPQCOF.cs index 86c5cc46..a92b4a37 100644 --- a/OpenDiablo2.Common/Models/MPQCOF.cs +++ b/OpenDiablo2.Common/Models/MPQCOF.cs @@ -57,6 +57,10 @@ namespace OpenDiablo2.Common.Models public IEnumerable Layers { get; private set; } public IEnumerable AnimationFrames { get; private set; } + public byte[] Priority { get; private set; } + public int NumberOfDirections { get; internal set; } + public int FramesPerDirection { get; internal set; } + public int NumberOfLayers { get; internal set; } public static MPQCOF Load(Stream stream, Dictionary> animations, eHero hero, eWeaponClass weaponClass, eMobMode mobMode, string ShieldCode, string weaponCode) { @@ -69,14 +73,14 @@ namespace OpenDiablo2.Common.Models var br = new BinaryReader(stream); - var numLayers = br.ReadByte(); - var framesPerDir = br.ReadByte(); - br.ReadByte(); // Number of directions + result.NumberOfLayers = br.ReadByte(); + result.FramesPerDirection = br.ReadByte(); + result.NumberOfDirections = br.ReadByte(); // Number of directions br.ReadBytes(25); // Skip 25 unknown bytes... var layers = new List(); - for (var layerIdx = 0; layerIdx < numLayers; layerIdx++) + for (var layerIdx = 0; layerIdx < result.NumberOfLayers; layerIdx++) { var layer = new COFLayer { @@ -93,7 +97,8 @@ namespace OpenDiablo2.Common.Models layer.WeaponCode = weaponCode; } result.Layers = layers; - result.AnimationFrames = br.ReadBytes(framesPerDir).Select(x => (eAnimationFrame)x); + result.AnimationFrames = br.ReadBytes(result.FramesPerDirection).Select(x => (eAnimationFrame)x); + result.Priority = br.ReadBytes(result.FramesPerDirection * result.NumberOfLayers * result.NumberOfDirections); var cofName = $"{hero.ToToken()}{mobMode.ToToken()}{weaponClass.ToToken()}".ToUpper(); result.Animations = animations[cofName]; diff --git a/OpenDiablo2.Core/GameEngine.cs b/OpenDiablo2.Core/GameEngine.cs index a1bcaa93..92062193 100644 --- a/OpenDiablo2.Core/GameEngine.cs +++ b/OpenDiablo2.Core/GameEngine.cs @@ -112,6 +112,7 @@ namespace OpenDiablo2.Core if (nextScene!= null) { + currentScene.Dispose(); currentScene = nextScene; nextScene = null; continue; @@ -131,6 +132,13 @@ namespace OpenDiablo2.Core } public void ChangeScene(eSceneType sceneType) - => nextScene = getScene(sceneType); + { + var loadingSprite = getRenderWindow().LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading, new Point(300, 400)); + + getRenderWindow().Clear(); + getRenderWindow().Draw(loadingSprite); + getRenderWindow().Sync(); + nextScene = getScene(sceneType); + } } } diff --git a/OpenDiablo2.Core/GameState/GameState.cs b/OpenDiablo2.Core/GameState/GameState.cs index 0c4e8a93..c15a76e6 100644 --- a/OpenDiablo2.Core/GameState/GameState.cs +++ b/OpenDiablo2.Core/GameState/GameState.cs @@ -269,10 +269,8 @@ namespace OpenDiablo2.Core.GameState_ private IMapInfo GetMap(ref int cellX, ref int cellY) { - var x = cellX; - var y = cellY; - var map = mapInfo.LastOrDefault(z => (x >= z.TileLocation.X) && (y >= z.TileLocation.Y) - && (x < z.TileLocation.Right) && (y < z.TileLocation.Bottom)); + var p = new Point(cellX, cellY); + var map = mapInfo.LastOrDefault(z => z.TileLocation.Contains(p)); if (map == null) { return null; diff --git a/OpenDiablo2.Core/ResourceManager.cs b/OpenDiablo2.Core/ResourceManager.cs index 1ab68c8f..5264211c 100644 --- a/OpenDiablo2.Core/ResourceManager.cs +++ b/OpenDiablo2.Core/ResourceManager.cs @@ -43,7 +43,8 @@ namespace OpenDiablo2.Core } public ImageSet GetImageSet(string resourcePath) - => cache.AddOrGetExisting($"ImageSet::{resourcePath}", () => ImageSet.LoadFromStream(mpqProvider.GetStream(resourcePath))); + // => cache.AddOrGetExisting($"ImageSet::{resourcePath}", () => ImageSet.LoadFromStream(mpqProvider.GetStream(resourcePath))); + => ImageSet.LoadFromStream(mpqProvider.GetStream(resourcePath)); public MPQFont GetMPQFont(string resourcePath) => cache.AddOrGetExisting($"Font::{resourcePath}", () => MPQFont.LoadFromStream(mpqProvider.GetStream($"{resourcePath}.DC6"), mpqProvider.GetStream($"{resourcePath}.tbl"))); @@ -68,46 +69,29 @@ namespace OpenDiablo2.Core { var path = $"{ResourcePaths.PlayerAnimationBase}\\{hero.ToToken()}\\COF\\{hero.ToToken()}{mobMode.ToToken()}{weaponClass.ToToken()}.cof"; return MPQCOF.Load(mpqProvider.GetStream(path), Animations, hero, weaponClass, mobMode, shieldCode, weaponCode); - }); + }, new System.Runtime.Caching.CacheItemPolicy { Priority = System.Runtime.Caching.CacheItemPriority.NotRemovable }); public MPQDCC GetPlayerDCC(MPQCOF.COFLayer cofLayer, eArmorType armorType, Palette palette) { - // TODO: We need to cache this... - byte[] binaryData; + return cache.AddOrGetExisting($"PlayerDCC::{cofLayer.GetDCCPath(armorType)}", () => + { + byte[] binaryData; - var streamPath = cofLayer.GetDCCPath(armorType); - using (var stream = mpqProvider.GetStream(streamPath)) - { - if (stream == null) - { - log.Error($"Could not load Player DCC: {streamPath}"); - return null; - } + var streamPath = cofLayer.GetDCCPath(armorType); + using (var stream = mpqProvider.GetStream(streamPath)) + { + if (stream == null) + { + log.Error($"Could not load Player DCC: {streamPath}"); + return null; + } - binaryData = new byte[stream.Length]; - stream.Read(binaryData, 0, (int)stream.Length); - } - var result = new MPQDCC(binaryData, palette); - return result; + binaryData = new byte[stream.Length]; + stream.Read(binaryData, 0, (int)stream.Length); + } + var result = new MPQDCC(binaryData, palette); + return result; + }); } - - /* - => cache.AddOrGetExisting($"DCC::{cofLayer}::{armorType}::{palette.Name}", () => - { - 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); - } - var result = new MPQDCC(binaryData, palette); - return result; - }); - /* - */ } } diff --git a/OpenDiablo2.SDL2/AutofacModule.cs b/OpenDiablo2.SDL2/AutofacModule.cs index e26d37cc..c837dfbf 100644 --- a/OpenDiablo2.SDL2/AutofacModule.cs +++ b/OpenDiablo2.SDL2/AutofacModule.cs @@ -13,7 +13,7 @@ namespace OpenDiablo2.SDL2_ log.Info("Configuring OpenDiablo2.Core service implementations."); builder.RegisterType().AsImplementedInterfaces().SingleInstance(); - builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); } } } diff --git a/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs b/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs index 7975826b..cabd7f9a 100644 --- a/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs +++ b/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs @@ -38,7 +38,6 @@ namespace OpenDiablo2.SDL2_ 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); @@ -57,6 +56,7 @@ namespace OpenDiablo2.SDL2_ private readonly List directionCache = new List(); DirectionCacheItem currentDirectionCache; private float seconds; + private int renderFrameIndex = 0; private readonly IResourceManager resourceManager; private readonly IPaletteProvider paletteProvider; @@ -79,13 +79,13 @@ namespace OpenDiablo2.SDL2_ 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 + x = pixelOffsetX + currentDirectionCache.SpriteRect[renderFrameIndex].x, + y = pixelOffsetY + currentDirectionCache.SpriteRect[renderFrameIndex].y, + w = currentDirectionCache.SpriteRect[renderFrameIndex].w, + h = currentDirectionCache.SpriteRect[renderFrameIndex].h }; - SDL.SDL_RenderCopy(renderer, currentDirectionCache.SpriteTexture[currentDirectionCache.RenderFrameIndex], IntPtr.Zero, ref destRect); + SDL.SDL_RenderCopy(renderer, currentDirectionCache.SpriteTexture[renderFrameIndex], IntPtr.Zero, ref destRect); } public void Update(long ms) @@ -98,9 +98,9 @@ namespace OpenDiablo2.SDL2_ while (seconds >= animationSeg) { seconds -= animationSeg; - currentDirectionCache.RenderFrameIndex++; - if (currentDirectionCache.RenderFrameIndex >= currentDirectionCache.FramesToAnimate) - currentDirectionCache.RenderFrameIndex = 0; + renderFrameIndex++; + while (renderFrameIndex >= currentDirectionCache.FramesToAnimate) + renderFrameIndex -= currentDirectionCache.FramesToAnimate; } } @@ -111,6 +111,7 @@ namespace OpenDiablo2.SDL2_ public void ResetAnimationData() { + var lastMobMode = MobMode; switch (LocationDetails.MovementType) { case eMovementType.Stopped: @@ -126,23 +127,22 @@ namespace OpenDiablo2.SDL2_ MobMode = eMobMode.PlayerNeutral; break; } + if (lastMobMode != MobMode) + renderFrameIndex = 0; currentDirectionCache = directionCache.FirstOrDefault(x => x.MobMode == MobMode && x.Direction == directionConversion[LocationDetails.MovementDirection]); if (currentDirectionCache != null) - { - currentDirectionCache.RenderFrameIndex = 0; return; - } animationData = resourceManager.GetPlayerAnimation(Hero, WeaponClass, MobMode, ShieldCode, WeaponCode); if (animationData == null) throw new OpenDiablo2Exception("Could not locate animation for the character!"); var palette = paletteProvider.PaletteTable["Units"]; - CacheFrames(animationData.Layers.Select(layer => resourceManager.GetPlayerDCC(layer, ArmorType, palette))); + CacheFrames(animationData.Layers.Select(layer => resourceManager.GetPlayerDCC(layer, ArmorType, palette)).ToArray()); } - private unsafe void CacheFrames(IEnumerable layerData) + private unsafe void CacheFrames(MPQDCC[] layerData) { var directionCache = new DirectionCacheItem { @@ -155,7 +155,6 @@ namespace OpenDiablo2.SDL2_ var dirAnimation = animationData.Animations[0]; directionCache.FramesToAnimate = dirAnimation.FramesPerDirection; directionCache.AnimationSpeed = dirAnimation.AnimationSpeed; - directionCache.RenderFrameIndex = 0; var minX = Int32.MaxValue; var minY = Int32.MaxValue; @@ -163,7 +162,6 @@ namespace OpenDiablo2.SDL2_ var maxY = Int32.MinValue; var layersIgnored = 0; - var layersToRender = new List(); foreach (var layer in layerData) { if (layer == null) @@ -172,7 +170,6 @@ namespace OpenDiablo2.SDL2_ continue; } - layersToRender.Add(layer); 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); @@ -187,7 +184,7 @@ namespace OpenDiablo2.SDL2_ directionCache.SpriteTexture = new IntPtr[directionCache.FramesToAnimate]; directionCache.SpriteRect = new SDL.SDL_Rect[directionCache.FramesToAnimate]; - + for (var frameIndex = 0; frameIndex < directionCache.FramesToAnimate; frameIndex++) { var texture = SDL.SDL_CreateTexture(renderer, SDL.SDL_PIXELFORMAT_ARGB8888, (int)SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, frameW, frameH); @@ -195,8 +192,21 @@ namespace OpenDiablo2.SDL2_ SDL.SDL_LockTexture(texture, IntPtr.Zero, out IntPtr pixels, out int pitch); UInt32* data = (UInt32*)pixels; - foreach (var layer in layersToRender) + var priorities = new int[animationData.NumberOfLayers]; + Array.Copy( + animationData.Priority, + (directionConversion[LocationDetails.MovementDirection] * animationData.FramesPerDirection * animationData.NumberOfLayers) + + (frameIndex * animationData.NumberOfLayers), + priorities, + 0, + animationData.NumberOfLayers + ); + + for (var i = 0; i < layerData.Length; i++) { + //var layer = layerData[priorities[i]]; + var layer = layerData[i]; + if (layer == null) continue; @@ -221,18 +231,19 @@ namespace OpenDiablo2.SDL2_ } } - + } SDL.SDL_UnlockTexture(texture); SDL.SDL_SetTextureBlendMode(texture, SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND); - + directionCache.SpriteTexture[frameIndex] = texture; directionCache.SpriteRect[frameIndex] = new SDL.SDL_Rect { x = minX, y = minY, w = frameW, h = frameH }; this.directionCache.Add(directionCache); - currentDirectionCache = directionCache; } + + currentDirectionCache = directionCache; } } diff --git a/OpenDiablo2.SDL2/SDL2SoundProvider.cs b/OpenDiablo2.SDL2/SDL2SoundProvider.cs index 5e2c6e51..2d635239 100644 --- a/OpenDiablo2.SDL2/SDL2SoundProvider.cs +++ b/OpenDiablo2.SDL2/SDL2SoundProvider.cs @@ -22,14 +22,14 @@ using SDL2; namespace OpenDiablo2.SDL2_ { - public sealed class SDL2MusicProvider : ISoundProvider + public sealed class SDL2SoundProvider : ISoundProvider { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private IntPtr music = IntPtr.Zero; private int musicChannel; private byte[] musicBytes; // Cannot be local or GC will destory it with great anger - public SDL2MusicProvider() + public SDL2SoundProvider() { if (SDL_mixer.Mix_OpenAudio(22050, SDL_mixer.MIX_DEFAULT_FORMAT, 2, 1024) < 0) log.Error($"SDL_mixer could not initialize! SDL_mixer Error: {SDL.SDL_GetError()}"); diff --git a/OpenDiablo2.SDL2/SDL2Sprite.cs b/OpenDiablo2.SDL2/SDL2Sprite.cs index 2a288cff..e608ba23 100644 --- a/OpenDiablo2.SDL2/SDL2Sprite.cs +++ b/OpenDiablo2.SDL2/SDL2Sprite.cs @@ -26,7 +26,7 @@ namespace OpenDiablo2.SDL2_ { internal sealed class SDL2Sprite : ISprite { - internal readonly ImageSet source; + internal ImageSet source; private readonly IntPtr renderer; private readonly bool cacheFrames; @@ -96,7 +96,7 @@ namespace OpenDiablo2.SDL2_ SDL.SDL_SetTextureBlendMode(texture[i], blend ? SDL.SDL_BlendMode.SDL_BLENDMODE_ADD : SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND); } else - if (texture[TextureIndex] != IntPtr.Zero) + if (texture != null && texture[TextureIndex] != IntPtr.Zero) SDL.SDL_SetTextureBlendMode(texture[TextureIndex], blend ? SDL.SDL_BlendMode.SDL_BLENDMODE_ADD : SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND); } @@ -206,6 +206,23 @@ namespace OpenDiablo2.SDL2_ var framestoClear = cacheFrames ? TotalFrames : 1; for (int i = 0; i < framestoClear; i++) frameLoaded[i] = false; + + DestroyTextures(); + } + + private void DestroyTextures() + { + var framestoClear = cacheFrames ? TotalFrames : 1; + for (var i = 0; i < framestoClear; i++) + { + if (!frameLoaded[i]) + continue; + SDL.SDL_DestroyTexture(texture[i]); + texture[i] = IntPtr.Zero; + frameLoaded[i] = false; + } + + texture = new IntPtr[TotalFrames]; } public void Dispose() @@ -213,16 +230,11 @@ namespace OpenDiablo2.SDL2_ if (disposed) return; - var framestoClear = cacheFrames ? TotalFrames : 1; - for (var i = 0; i < framestoClear; i++) - { - SDL.SDL_DestroyTexture(texture[i]); - texture[i] = IntPtr.Zero; - } - - texture = Array.Empty(); + DestroyTextures(); + source = null; disposed = true; } + } } diff --git a/OpenDiablo2.Scenes/MainMenu.cs b/OpenDiablo2.Scenes/MainMenu.cs index 9561959e..49dc0d09 100644 --- a/OpenDiablo2.Scenes/MainMenu.cs +++ b/OpenDiablo2.Scenes/MainMenu.cs @@ -77,18 +77,6 @@ namespace OpenDiablo2.Scenes urlLabel = renderWindow.CreateLabel(labelFont, new Point(50, 569), "https://github.com/essial/OpenDiablo2/"); urlLabel.TextColor = Color.Magenta; - var loadingSprite = renderWindow.LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading, new Point(300, 400)); - - // Pre-load all the scenes for now until we fix the sdl load problem - var scenesToLoad = new eSceneType[] { eSceneType.SelectHeroClass }; - for (int i = 0; i < scenesToLoad.Count(); i++) - { - renderWindow.Clear(); - renderWindow.Draw(loadingSprite, (int)(loadingSprite.TotalFrames * (i / (float)scenesToLoad.Count()))); - renderWindow.Sync(); - getScene(scenesToLoad[i]); - } - soundProvider.LoadSong(mpqProvider.GetStream(ResourcePaths.BGMTitle)); soundProvider.PlaySong(); } diff --git a/OpenDiablo2.Scenes/SelectHeroClass.cs b/OpenDiablo2.Scenes/SelectHeroClass.cs index 5fa3d1c8..cf29d2cb 100644 --- a/OpenDiablo2.Scenes/SelectHeroClass.cs +++ b/OpenDiablo2.Scenes/SelectHeroClass.cs @@ -20,13 +20,25 @@ namespace OpenDiablo2.Scenes Retreating } - class HeroRenderInfo + class HeroRenderInfo : IDisposable { public ISprite IdleSprite, IdleSelectedSprite, ForwardWalkSprite, ForwardWalkSpriteOverlay, SelectedSprite, SelectedSpriteOverlay, BackWalkSprite, BackWalkSpriteOverlay; public eHeroStance Stance; public long ForwardWalkTimeMs, BackWalkTimeMs; public long SpecialFrameTime; public Rectangle SelectionBounds = new Rectangle(); + + public void Dispose() + { + IdleSprite?.Dispose(); + IdleSelectedSprite?.Dispose(); + ForwardWalkSprite?.Dispose(); + ForwardWalkSpriteOverlay?.Dispose(); + SelectedSprite?.Dispose(); + SelectedSpriteOverlay?.Dispose(); + BackWalkSprite?.Dispose(); + BackWalkSpriteOverlay?.Dispose(); + } } [Scene(eSceneType.SelectHeroClass)] @@ -267,7 +279,7 @@ namespace OpenDiablo2.Scenes StopSfx(); var heros = Enum.GetValues(typeof(eHero)).Cast(); - foreach(var hero in heros) + foreach (var hero in heros) { heroRenderInfo[hero].SpecialFrameTime = 0; heroRenderInfo[hero].Stance = eHeroStance.Idle; @@ -460,7 +472,7 @@ namespace OpenDiablo2.Scenes renderInfo.SpecialFrameTime = 0; - foreach(var ri in heroRenderInfo) + foreach (var ri in heroRenderInfo) { if (ri.Value.Stance != eHeroStance.Selected) continue; @@ -611,6 +623,12 @@ namespace OpenDiablo2.Scenes headingFont.Dispose(); headingLabel.Dispose(); sfxDictionary.Clear(); + + foreach (var hri in heroRenderInfo) + hri.Value.Dispose(); + + heroRenderInfo.Clear(); } + } } diff --git a/OpenDiablo2/App.config b/OpenDiablo2/App.config index 5472be61..4a082533 100644 --- a/OpenDiablo2/App.config +++ b/OpenDiablo2/App.config @@ -1,8 +1,18 @@  - - - + + + + + + + + + +