diff --git a/PathfinderAPI/Event/Menu/DrawMainMenuTitlesEvent.cs b/PathfinderAPI/Event/Menu/DrawMainMenuTitlesEvent.cs index 825a4b4b..3ad72c66 100644 --- a/PathfinderAPI/Event/Menu/DrawMainMenuTitlesEvent.cs +++ b/PathfinderAPI/Event/Menu/DrawMainMenuTitlesEvent.cs @@ -7,6 +7,7 @@ using Hacknet.Effects; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using Pathfinder.GUI; namespace Pathfinder.Event.Menu { @@ -23,6 +24,8 @@ public class DrawMainMenuTitlesEvent : MainMenuEvent delegate void EmitDelegate(MainMenu menu, ref Rectangle rect); + private static AnimatedTexture anim = null; + [HarmonyILManipulator] [HarmonyPatch(typeof(MainMenu), nameof(MainMenu.DrawBackgroundAndTitle))] internal static void onDrawMainMenuTitlesIL(ILContext il) @@ -71,6 +74,13 @@ internal static void onDrawMainMenuTitlesIL(ILContext il) main.Color ); TextItem.doFontLabel(new Vector2(sub.Destination.Location.X, sub.Destination.Location.Y), sub.Title, sub.Font, sub.Color, 600f, 26f); + + if (anim == null) + { + anim = AnimatedTexture.ReadFromFile("swirl.gif"); + anim.ShouldLoop = true; + } + anim.Draw(1f / 60f, GuiData.spriteBatch); }); var firstLabel = c.MarkLabel(); diff --git a/PathfinderAPI/GUI/AnimatedTexture.cs b/PathfinderAPI/GUI/AnimatedTexture.cs new file mode 100644 index 00000000..075f9078 --- /dev/null +++ b/PathfinderAPI/GUI/AnimatedTexture.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.IO.Compression; +using System.Linq; +using Hacknet; +using Hacknet.Gui; +using Microsoft.Xna.Framework.Graphics; +using Pathfinder.Util; +using Color = Microsoft.Xna.Framework.Color; +using Rectangle = Microsoft.Xna.Framework.Rectangle; + +namespace Pathfinder.GUI +{ + public class AnimatedTexture : IDisposable + { + public Texture2D[] Frames { get; } + public int Framerate { get; set; } + private float frameTime; + public bool ShouldLoop { get; set; } + public Rectangle DestRect { get; set; } + public bool Paused { get; set; } + + public AnimatedTexture(IEnumerable frames, Rectangle dest, int framerate, bool loop = false, bool startPaused = false) + { + Frames = frames?.ToArray() ?? throw new ArgumentNullException(nameof(frames)); + DestRect = dest; + Framerate = framerate; + frameTime = 1f / framerate; + ShouldLoop = loop; + Paused = startPaused; + } + + private float timeSinceLastFrame = 0f; + private int currentFrame = 0; + public void Draw(float t, SpriteBatch spriteBatch) + { + if (Paused) + return; + + timeSinceLastFrame += t; + if (timeSinceLastFrame > frameTime) + { + currentFrame++; + timeSinceLastFrame = 0f; + } + + if (currentFrame < 0 || currentFrame >= Frames.Length) + { + currentFrame = 0; + Paused = !ShouldLoop; + if (Paused) + return; + } + + spriteBatch.Draw(Frames[0], DestRect, Color.White); + } + + public static AnimatedTexture ReadFromFile(string filename) + { + var stream = new FileStream(filename, FileMode.Open); + var ret = ReadFromStream(stream); + stream.Dispose(); + return ret; + } + + public static AnimatedTexture ReadFromStream(Stream stream) + { + var frameList = new List(); + + int width = 0; + int height = 0; + using (var image = new Bitmap(stream)) + { + width = image.Width; + height = image.Height; + for (int i = 0; i < image.GetFrameCount(FrameDimension.Time); i++) + { + image.SelectActiveFrame(FrameDimension.Page, i); + var data = image.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + Color[] colors = new Color[height * width]; + unsafe + { + Color* ptr = (Color*)data.Scan0.ToPointer(); + for (int arrayIndex = 0; arrayIndex < colors.Length; arrayIndex++) + { + Color color = *ptr++; + colors[arrayIndex] = new Color(color.B, color.G, color.R, color.A); + } + } + image.UnlockBits(data); + var frame = new Texture2D(GuiData.spriteBatch.GraphicsDevice, width, height, false, SurfaceFormat.Color); + frame.SetData(colors); + frameList.Add(frame); + } + } + + return new AnimatedTexture(frameList, new Rectangle(0, 0, width, height), 30); + } + + public void Dispose() + { + foreach (var frame in Frames) + frame.Dispose(); + } + } +} diff --git a/PathfinderAPI/GUI/PFButton.cs b/PathfinderAPI/GUI/PFButton.cs index a0f0dbd9..a76716fd 100644 --- a/PathfinderAPI/GUI/PFButton.cs +++ b/PathfinderAPI/GUI/PFButton.cs @@ -60,7 +60,7 @@ public bool Do() public void Dispose() { - returnedIds.Add(ID); + ReturnID(ID); invalid = true; } } diff --git a/PathfinderAPI/PathfinderAPI.csproj b/PathfinderAPI/PathfinderAPI.csproj index 473fd552..28435280 100644 --- a/PathfinderAPI/PathfinderAPI.csproj +++ b/PathfinderAPI/PathfinderAPI.csproj @@ -12,6 +12,7 @@ v4.0 512 true + true true @@ -57,6 +58,7 @@ + @@ -102,6 +104,7 @@ + @@ -117,10 +120,12 @@ + + diff --git a/PathfinderAPI/PathfinderAPIPlugin.cs b/PathfinderAPI/PathfinderAPIPlugin.cs index bd497376..024adf45 100644 --- a/PathfinderAPI/PathfinderAPIPlugin.cs +++ b/PathfinderAPI/PathfinderAPIPlugin.cs @@ -3,7 +3,10 @@ using BepInEx; using BepInEx.Configuration; using BepInEx.Hacknet; +using Hacknet; using HarmonyLib; +using Microsoft.Xna.Framework; +using Pathfinder.GUI; using Pathfinder.Util; namespace Pathfinder diff --git a/PathfinderAPI/Util/BitReader.cs b/PathfinderAPI/Util/BitReader.cs new file mode 100644 index 00000000..f691469b --- /dev/null +++ b/PathfinderAPI/Util/BitReader.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; + +namespace Pathfinder.Util +{ + public class BitReader : IDisposable + { + private BinaryReader Reader; + + private byte currentByte = 0; + private int offset = 8; + + public BitReader(Stream stream) + { + Reader = new BinaryReader(stream); + } + + public byte ReadByte() + { + if (offset == 8) + { + return Reader.ReadByte(); + } + return (byte)ReadBitsDepth(8); + } + + public byte[] ReadBytes(int count) + { + byte[] ret = new byte[count]; + for (int i = 0; i < count; i++) + { + ret[i] = ReadByte(); + } + + return ret; + } + + public ushort ReadUInt16() + { + ushort ret = 0; + for (int i = 0; i < 2 ; i++) + { + ret |= (ushort)(ReadByte() << i * 8); + } + + return ret; + } + + public int ReadBit() + { + if (offset == 8) + { + currentByte = Reader.ReadByte(); + offset = 0; + } + + return (currentByte >> offset++) & 1; + } + + public uint ReadBitsDepth(int bitDepth) + { + byte ret = 0; + for (int i = 0; i < bitDepth; i++) + { + ret |= (byte)(ReadBit() << i); + } + + return ret; + } + + public void Dispose() + { + Reader.Dispose(); + } + } +} \ No newline at end of file diff --git a/PathfinderAPI/Util/SBAwareBinaryReader.cs b/PathfinderAPI/Util/SBAwareBinaryReader.cs new file mode 100644 index 00000000..a2283b55 --- /dev/null +++ b/PathfinderAPI/Util/SBAwareBinaryReader.cs @@ -0,0 +1,112 @@ +using System; +using System.IO; +using System.Linq; + +namespace Pathfinder.Util +{ + public class SBAwareBinaryReader : BinaryReader + { + public enum Endianess + { + LITTLE, + BIG + } + + public Endianess Endian; + + public SBAwareBinaryReader(Stream stream, Endianess endian) : base(stream) + { + Endian = endian; + } + + public override short ReadInt16() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadInt16(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToInt16(ReadBytes(2).Reverse().ToArray(), 0); + } + + return BitConverter.ToInt16(ReadBytes(2), 0); + } + + public override ushort ReadUInt16() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadUInt16(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToUInt16(ReadBytes(2).Reverse().ToArray(), 0); + } + + return BitConverter.ToUInt16(ReadBytes(2), 0); + } + + public override int ReadInt32() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadInt32(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToInt32(ReadBytes(4).Reverse().ToArray(), 0); + } + + return BitConverter.ToInt32(ReadBytes(4), 0); + } + + public override uint ReadUInt32() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadUInt32(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToUInt32(ReadBytes(4).Reverse().ToArray(), 0); + } + + return BitConverter.ToUInt32(ReadBytes(4), 0); + } + + public override long ReadInt64() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadInt64(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToInt64(ReadBytes(8).Reverse().ToArray(), 0); + } + + return BitConverter.ToInt64(ReadBytes(8), 0); + } + + public override ulong ReadUInt64() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadUInt64(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToUInt64(ReadBytes(8).Reverse().ToArray(), 0); + } + + return BitConverter.ToUInt64(ReadBytes(8), 0); + } + } +} \ No newline at end of file