diff --git a/LemonUI/Elements/BaseElement.cs b/LemonUI/Elements/BaseElement.cs index 55222526..208911b8 100644 --- a/LemonUI/Elements/BaseElement.cs +++ b/LemonUI/Elements/BaseElement.cs @@ -1,5 +1,5 @@ -using LemonUI.Extensions; using System.Drawing; +using LemonUI.Tools; namespace LemonUI.Elements { diff --git a/LemonUI/Elements/ScaledText.cs b/LemonUI/Elements/ScaledText.cs index 481c0e99..05fffb2f 100644 --- a/LemonUI/Elements/ScaledText.cs +++ b/LemonUI/Elements/ScaledText.cs @@ -16,7 +16,7 @@ using System.Collections.Generic; using System.Drawing; using System.Text; -using LemonUI.Extensions; +using LemonUI.Tools; namespace LemonUI.Elements { @@ -37,9 +37,9 @@ public class ScaledText : IText #region Fields /// - /// The absolute 1080p based screen position. + /// The scaled 1080p based screen position. /// - private PointF absolutePosition = PointF.Empty; + private PointF scaledPosition = PointF.Empty; /// /// The relative 0-1 relative position. /// @@ -74,10 +74,10 @@ public class ScaledText : IText /// public PointF Position { - get => absolutePosition; + get => scaledPosition; set { - absolutePosition = value; + scaledPosition = value; relativePosition = value.ToRelative(); } } @@ -150,23 +150,23 @@ public float Width #if FIVEM API.BeginTextCommandWidth("CELL_EMAIL_BCON"); Add(); - return API.EndTextCommandGetWidth(true) * 1f.ToXAbsolute(); + return API.EndTextCommandGetWidth(true) * 1f.ToXScaled(); #elif ALTV Alt.Natives.BeginTextCommandGetScreenWidthOfDisplayText("CELL_EMAIL_BCON"); Add(); - return Alt.Natives.EndTextCommandGetScreenWidthOfDisplayText(true) * 1f.ToXAbsolute(); + return Alt.Natives.EndTextCommandGetScreenWidthOfDisplayText(true) * 1f.ToXScaled(); #elif RAGEMP Invoker.Invoke(Natives.BeginTextCommandWidth, "CELL_EMAIL_BCON"); Add(); - return Invoker.Invoke(Natives.EndTextCommandGetWidth) * 1f.ToXAbsolute(); + return Invoker.Invoke(Natives.EndTextCommandGetWidth) * 1f.ToXScaled(); #elif RPH NativeFunction.CallByHash(0x54CE8AC98E120CAB, "CELL_EMAIL_BCON"); Add(); - return NativeFunction.CallByHash(0x85F061DA64ED2F67, true) * 1f.ToXAbsolute(); + return NativeFunction.CallByHash(0x85F061DA64ED2F67, true) * 1f.ToXScaled(); #elif SHVDN3 || SHVDNC Function.Call(Hash.BEGIN_TEXT_COMMAND_GET_SCREEN_WIDTH_OF_DISPLAY_TEXT, "CELL_EMAIL_BCON"); Add(); - return Function.Call(Hash.END_TEXT_COMMAND_GET_SCREEN_WIDTH_OF_DISPLAY_TEXT, true) * 1f.ToXAbsolute(); + return Function.Call(Hash.END_TEXT_COMMAND_GET_SCREEN_WIDTH_OF_DISPLAY_TEXT, true) * 1f.ToXScaled(); #endif } } @@ -505,7 +505,7 @@ private void Slice() public void Recalculate() { // Do the normal Size and Position recalculation - relativePosition = absolutePosition.ToRelative(); + relativePosition = scaledPosition.ToRelative(); // And recalculate the word wrap if necessary if (internalWrap <= 0) { diff --git a/LemonUI/Extensions/FloatExtensions.cs b/LemonUI/Extensions/FloatExtensions.cs index 81bb74e6..ddc7340a 100644 --- a/LemonUI/Extensions/FloatExtensions.cs +++ b/LemonUI/Extensions/FloatExtensions.cs @@ -1,53 +1,36 @@ +using System; + namespace LemonUI.Extensions { /// /// Extensions for the float class. /// + [Obsolete("Please use LemonUI.Tools.Extensions instead.", true)] public static class FloatExtensions { - #region Extensions - /// - /// Converts an absolute X or Width float to a relative one. + /// Converts a scaled X or Width float to a relative one. /// /// The float to convert. /// A relative float between 0 and 1. - public static float ToXRelative(this float fin) - { - Screen.ToRelative(fin, 0, out float fout, out _); - return fout; - } + public static float ToXRelative(this float fin) => Tools.Extensions.ToXRelative(fin); /// - /// Converts an absolute Y or Height float to a relative one. + /// Converts a scaled Y or Height float to a relative one. /// /// The float to convert. /// A relative float between 0 and 1. - public static float ToYRelative(this float fin) - { - Screen.ToRelative(0, fin, out _, out float fout); - return fout; - } + public static float ToYRelative(this float fin) => Tools.Extensions.ToYRelative(fin); /// - /// Converts an relative X or Width float to an absolute one. + /// Converts an relative X or Width float to an scaled one. /// /// The float to convert. - /// An absolute float. - public static float ToXAbsolute(this float fin) - { - Screen.ToAbsolute(fin, 0, out float fout, out _); - return fout; - } + /// A scaled float. + public static float ToXAbsolute(this float fin) => Tools.Extensions.ToXScaled(fin); /// - /// Converts an relative Y or Height float to an absolute one. + /// Converts an relative Y or Height float to an scaled one. /// /// The float to convert. - /// An absolute float. - public static float ToYAbsolute(this float fin) - { - Screen.ToAbsolute(0, fin, out _, out float fout); - return fout; - } - - #endregion + /// A scaled float. + public static float ToYAbsolute(this float fin) => Tools.Extensions.ToYScaled(fin); } } diff --git a/LemonUI/Extensions/PointExtensions.cs b/LemonUI/Extensions/PointExtensions.cs index 5b81adf5..5f0c2673 100644 --- a/LemonUI/Extensions/PointExtensions.cs +++ b/LemonUI/Extensions/PointExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Drawing; namespace LemonUI.Extensions @@ -5,31 +6,20 @@ namespace LemonUI.Extensions /// /// Extensions for the Point and PointF classes. /// + [Obsolete("Please use LemonUI.Tools.Extensions instead.", true)] public static class PointExtensions { - #region Extensions - /// - /// Converts an absolute 1080-based position into a relative one. + /// Converts a scaled 1080-based position into a relative one. /// - /// The absolute PointF. + /// The scaled PointF. /// A new PointF with relative values. - public static PointF ToRelative(this PointF point) - { - Screen.ToRelative(point.X, point.Y, out float x, out float y); - return new PointF(x, y); - } + public static PointF ToRelative(this PointF point) => Tools.Extensions.ToRelative(point); /// - /// Converts a normalized 0-1 position into an absolute one. + /// Converts a normalized 0-1 position into a scaled one. /// /// The relative PointF. - /// A new PointF with absolute values. - public static PointF ToAbsolute(this PointF point) - { - Screen.ToAbsolute(point.X, point.Y, out float x, out float y); - return new PointF(x, y); - } - - #endregion + /// A new PointF with scaled values. + public static PointF ToAbsolute(this PointF point) => Tools.Extensions.ToScaled(point); } } diff --git a/LemonUI/Extensions/SizeExtensions.cs b/LemonUI/Extensions/SizeExtensions.cs index e2a893e1..0660a346 100644 --- a/LemonUI/Extensions/SizeExtensions.cs +++ b/LemonUI/Extensions/SizeExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Drawing; namespace LemonUI.Extensions @@ -5,31 +6,20 @@ namespace LemonUI.Extensions /// /// Extensions for the Size and SizeF classes. /// + [Obsolete("Please use LemonUI.Tools.Extensions instead.", true)] public static class SizeExtensions { - #region Extensions - /// - /// Converts an absolute 1080-based size into a relative one. + /// Converts a scaled 1080-based size into a relative one. /// - /// The absolute SizeF. + /// The scaled SizeF. /// A new SizeF with relative values. - public static SizeF ToRelative(this SizeF size) - { - Screen.ToRelative(size.Width, size.Height, out float width, out float height); - return new SizeF(width, height); - } + public static SizeF ToRelative(this SizeF size) => Tools.Extensions.ToRelative(size); /// - /// Converts a normalized 0-1 size into an absolute one. + /// Converts a normalized 0-1 size into a scaled one. /// /// The relative SizeF. - /// A new SizeF with absolute values. - public static SizeF ToAbsolute(this SizeF size) - { - Screen.ToAbsolute(size.Width, size.Height, out float width, out float height); - return new SizeF(width, height); - } - - #endregion + /// A new SizeF with scaled values. + public static SizeF ToAbsolute(this SizeF size) => Tools.Extensions.ToScaled(size); } } diff --git a/LemonUI/Menus/NativeGridPanel.cs b/LemonUI/Menus/NativeGridPanel.cs index 4605b353..afc3a787 100644 --- a/LemonUI/Menus/NativeGridPanel.cs +++ b/LemonUI/Menus/NativeGridPanel.cs @@ -12,13 +12,12 @@ using Control = Rage.GameControl; #elif SHVDN3 || SHVDNC using GTA; -using GTA.Native; using GTA.UI; #endif using LemonUI.Elements; -using LemonUI.Extensions; using System; using System.Drawing; +using LemonUI.Tools; namespace LemonUI.Menus { @@ -290,16 +289,16 @@ public override void Process() if (!Controls.IsUsingController) { - if (Screen.IsCursorInArea(grid.Position, grid.Size) && Controls.IsPressed(Control.CursorAccept)) + if (GameScreen.IsCursorInArea(grid.Position, grid.Size) && Controls.IsPressed(Control.CursorAccept)) { - PointF cursor = Screen.CursorPositionRelative; - PointF pos = innerPosition.ToRelative(); + PointF cursor = GameScreen.Cursor; + PointF pos = innerPosition; PointF start = new PointF(cursor.X - pos.X, cursor.Y - pos.Y); - SizeF size = innerSize.ToRelative(); + SizeF size = innerSize; - x = start.X / size.Width; - y = start.Y / size.Height; + x = (start.X / size.Width).ToXRelative(); + y = (start.Y / size.Height).ToYRelative(); } else { diff --git a/LemonUI/Menus/NativeItem.cs b/LemonUI/Menus/NativeItem.cs index 61b7999a..a36969b7 100644 --- a/LemonUI/Menus/NativeItem.cs +++ b/LemonUI/Menus/NativeItem.cs @@ -11,6 +11,7 @@ using LemonUI.Elements; using System; using System.Drawing; +using LemonUI.Tools; namespace LemonUI.Menus { @@ -230,7 +231,7 @@ public ColorSet Colors /// /// If this item is being hovered. /// - public bool IsHovered => Screen.IsCursorInArea(background.Position, background.Size); + public bool IsHovered => GameScreen.IsCursorInArea(background.Position, background.Size); #endregion diff --git a/LemonUI/Menus/NativeListItem{T}.cs b/LemonUI/Menus/NativeListItem{T}.cs index ee2a11dd..7c5d3b7c 100644 --- a/LemonUI/Menus/NativeListItem{T}.cs +++ b/LemonUI/Menus/NativeListItem{T}.cs @@ -73,7 +73,7 @@ public T SelectedItem { if (Items.Count == 0) { - throw new InvalidOperationException("There are no available items."); + return; } int newIndex = Items.IndexOf(value); diff --git a/LemonUI/Menus/NativeMenu.cs b/LemonUI/Menus/NativeMenu.cs index f6837732..c9412946 100644 --- a/LemonUI/Menus/NativeMenu.cs +++ b/LemonUI/Menus/NativeMenu.cs @@ -26,13 +26,13 @@ using Font = GTA.UI.Font; #endif using LemonUI.Elements; -using LemonUI.Extensions; using LemonUI.Scaleform; using System; using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Linq; +using LemonUI.Tools; namespace LemonUI.Menus { @@ -950,11 +950,10 @@ public void ResetCursor() const float extraX = 35; const float extraY = 325; - // Get the correct desired position of the cursor as relative PointF pos = PointF.Empty; if (SafeZoneAware) { - Screen.SetElementAlignment(Alignment, GFXAlignment.Top); + SafeZone.SetAlignment(Alignment, GFXAlignment.Top); float x = 0; switch (Alignment) { @@ -965,8 +964,8 @@ public void ResetCursor() x = Offset.X - Width - extraX; break; } - pos = Screen.GetRealPosition(x, Offset.Y + extraY).ToRelative(); - Screen.ResetElementAlignment(); + pos = SafeZone.GetSafePosition(x, Offset.Y + extraY).ToRelative(); + SafeZone.ResetAlignment(); } else { @@ -977,7 +976,7 @@ public void ResetCursor() x = Offset.X + Width + extraX; break; case Alignment.Right: - x = 1f.ToXAbsolute() - Offset.X - Width - extraX; + x = 1f.ToXScaled() - Offset.X - Width - extraX; break; } pos = new PointF(x, Offset.Y + extraY).ToRelative(); @@ -1004,7 +1003,7 @@ private void UpdateItems() PointF pos; if (SafeZoneAware) { - Screen.SetElementAlignment(Alignment, GFXAlignment.Top); + SafeZone.SetAlignment(Alignment, GFXAlignment.Top); float x = 0; switch (Alignment) { @@ -1015,8 +1014,8 @@ private void UpdateItems() x = Offset.X - Width; break; } - pos = Screen.GetRealPosition(x, Offset.Y); - Screen.ResetElementAlignment(); + pos = SafeZone.GetSafePosition(x, Offset.Y); + SafeZone.ResetAlignment(); } else { @@ -1027,7 +1026,7 @@ private void UpdateItems() x = Offset.X; break; case Alignment.Right: - x = 1f.ToXAbsolute() - Width - Offset.X; + x = 1f.ToXScaled() - Width - Offset.X; break; } pos = new PointF(x, Offset.Y); @@ -1208,18 +1207,12 @@ private void ProcessControls() if (UseMouse && !Controls.IsUsingController) { // Enable the mouse cursor -#if FIVEM || SHVDN3 || SHVDNC || ALTV - Screen.ShowCursorThisFrame(); -#elif RAGEMP - Invoker.Invoke(Natives.ShowCursorThisFrame); -#elif RPH - NativeFunction.CallByHash(0xAAE7CE1D63167423); -#endif + GameScreen.ShowCursorThisFrame(); // If the camera should be rotated when the cursor is on the left and right sections of the screen, do so if (RotateCamera) { - if (Screen.IsCursorInArea(PointF.Empty, searchAreaSize)) + if (GameScreen.IsCursorInArea(PointF.Empty, searchAreaSize)) { #if FIVEM || SHVDN3 || SHVDNC GameplayCamera.RelativeHeading += 5; @@ -1233,7 +1226,7 @@ private void ProcessControls() Camera.RenderingCamera.Heading += 5; #endif } - else if (Screen.IsCursorInArea(searchAreaRight, searchAreaSize)) + else if (GameScreen.IsCursorInArea(searchAreaRight, searchAreaSize)) { #if FIVEM || SHVDN3 || SHVDNC GameplayCamera.RelativeHeading -= 5; @@ -1259,7 +1252,7 @@ private void ProcessControls() if (item == selectedItem && item is NativeSlidableItem slidable) { // If the right arrow was pressed, go to the right - if (Screen.IsCursorInArea(slidable.RightArrow.Position, slidable.RightArrow.Size)) + if (GameScreen.IsCursorInArea(slidable.RightArrow.Position, slidable.RightArrow.Size)) { if (item.Enabled) { @@ -1273,7 +1266,7 @@ private void ProcessControls() return; } // If the user pressed the left arrow, go to the right - else if (Screen.IsCursorInArea(slidable.LeftArrow.Position, slidable.LeftArrow.Size)) + else if (GameScreen.IsCursorInArea(slidable.LeftArrow.Position, slidable.LeftArrow.Size)) { if (item.Enabled) { @@ -1289,7 +1282,7 @@ private void ProcessControls() } // If the cursor is inside of the selection rectangle - if (Screen.IsCursorInArea(item.title.Position.X - itemOffsetX, item.title.Position.Y - itemOffsetY, Width, itemHeight)) + if (GameScreen.IsCursorInArea(item.title.Position.X - itemOffsetX, item.title.Position.Y - itemOffsetY, Width, itemHeight)) { // If the item is selected, activate it if (item == selectedItem) @@ -1688,9 +1681,8 @@ public virtual void Recalculate() x = Offset.X - Width; break; } - Screen.SetElementAlignment(Alignment, GFXAlignment.Top); - pos = Screen.GetRealPosition(x, Offset.Y); - Screen.ResetElementAlignment(); + + pos = SafeZone.GetPositionAt(new PointF(x, Offset.Y), Alignment, GFXAlignment.Top); } else { @@ -1701,7 +1693,7 @@ public virtual void Recalculate() x = Offset.X; break; case Alignment.Right: - x = 1f.ToXAbsolute() - Width - Offset.X; + x = 1f.ToXScaled() - Width - Offset.X; break; } pos = new PointF(x, Offset.Y); @@ -1742,7 +1734,7 @@ public virtual void Recalculate() descriptionText.WordWrap = width - posXDescTxt; // Set the right size of the rotation - searchAreaRight = new PointF(1f.ToXAbsolute() - 30, 0); + searchAreaRight = new PointF(1f.ToXScaled() - 30, 0); // Then, continue with an item update UpdateItems(); diff --git a/LemonUI/ObjectPool.cs b/LemonUI/ObjectPool.cs index 4405dd47..cf4dbacc 100644 --- a/LemonUI/ObjectPool.cs +++ b/LemonUI/ObjectPool.cs @@ -15,6 +15,7 @@ using System.Collections; using System.Collections.Generic; using System.Drawing; +using LemonUI.Tools; namespace LemonUI { @@ -28,32 +29,11 @@ public class ObjectPool : IEnumerable /// /// The last known resolution by the object pool. /// -#if FIVEM - private SizeF lastKnownResolution = CitizenFX.Core.UI.Screen.Resolution; -#elif ALTV - private SizeF lastKnownResolution = Screen.Resolution; -#elif RAGEMP - private SizeF lastKnownResolution = new SizeF(Game.ScreenResolution.Width, Game.ScreenResolution.Height); -#elif RPH - private SizeF lastKnownResolution = Game.Resolution; -#elif SHVDN3 || SHVDNC - private SizeF lastKnownResolution = GTA.UI.Screen.Resolution; -#endif + private SizeF lastKnownResolution = GameScreen.AbsoluteResolution; /// /// The last know Safezone size. /// -#if FIVEM - private float lastKnownSafezone = API.GetSafeZoneSize(); -#elif ALTV - private float lastKnownSafezone = Alt.Natives.GetSafeZoneSize(); -#elif RAGEMP - private float lastKnownSafezone = Invoker.Invoke(Natives.GetSafeZoneSize); -#elif RPH - private float lastKnownSafezone = NativeFunction.CallByHash(0xBAF107B6BB2C97F0); -#elif SHVDN3 || SHVDNC - private float lastKnownSafezone = Function.Call(Hash.GET_SAFE_ZONE_SIZE); -#endif - + private float lastKnownSafezone = SafeZone.Size; /// /// The list of processable objects. /// @@ -104,18 +84,7 @@ public bool AreAnyVisible private void DetectResolutionChanges() { // Get the current resolution -#if FIVEM - SizeF resolution = CitizenFX.Core.UI.Screen.Resolution; -#elif ALTV - SizeF resolution = Screen.Resolution; -#elif RAGEMP - ScreenResolutionType raw = Game.ScreenResolution; - SizeF resolution = new SizeF(raw.Width, raw.Height); -#elif RPH - SizeF resolution = Game.Resolution; -#elif SHVDN3 || SHVDNC - SizeF resolution = GTA.UI.Screen.Resolution; -#endif + SizeF resolution = GameScreen.AbsoluteResolution; // If the old res does not matches the current one if (lastKnownResolution != resolution) { @@ -133,18 +102,7 @@ private void DetectResolutionChanges() private void DetectSafezoneChanges() { // Get the current Safezone size -#if FIVEM - float safezone = API.GetSafeZoneSize(); -#elif ALTV - float safezone = Alt.Natives.GetSafeZoneSize(); -#elif RAGEMP - float safezone = Invoker.Invoke(Natives.GetSafeZoneSize); -#elif RPH - float safezone = NativeFunction.CallByHash(0xBAF107B6BB2C97F0); -#elif SHVDN3 || SHVDNC - float safezone = Function.Call(Hash.GET_SAFE_ZONE_SIZE); -#endif - + float safezone = SafeZone.Size; // If is not the same as the last one if (lastKnownSafezone != safezone) { diff --git a/LemonUI/Screen.cs b/LemonUI/Screen.cs index 27acfe79..b7c893d8 100644 --- a/LemonUI/Screen.cs +++ b/LemonUI/Screen.cs @@ -16,15 +16,16 @@ using AltV.Net.Client; using LemonUI.Elements; #endif -using LemonUI.Extensions; using System; using System.Drawing; +using LemonUI.Tools; namespace LemonUI { /// /// Contains a set of tools to work with the screen information. /// + [Obsolete("Use the LemonUI.Tools and LemonUI.Math namespaces.")] public static class Screen { #region Properties @@ -32,65 +33,17 @@ public static class Screen /// /// The Aspect Ratio of the screen resolution. /// - public static float AspectRatio - { - get - { -#if FIVEM - return API.GetAspectRatio(false); -#elif RAGEMP - return Invoker.Invoke(Natives.GetAspectRatio); -#elif RPH - return NativeFunction.CallByHash(0xF1307EF624A80D87, false); -#elif SHVDN3 || SHVDNC - return Function.Call(Hash.GET_ASPECT_RATIO, false); -#elif ALTV - return Alt.Natives.GetAspectRatio(false); -#endif - } - } - + public static float AspectRatio => GameScreen.AspectRatio; #if ALTV /// - /// Gets the actual Screen resolution the game is being rendered at + /// Gets the actual Screen resolution the game is being rendered at. /// - public static Size Resolution - { - get - { - int height = 0, width = 0; - Alt.Natives.GetActualScreenResolution(ref width, ref height); - return new Size(width, height); - } - } + public static Size Resolution => GameScreen.AbsoluteResolution.ToSize(); #endif - /// /// The location of the cursor on screen between 0 and 1. /// - public static PointF CursorPositionRelative - { - get - { -#if FIVEM - float cursorX = API.GetControlNormal(0, (int)Control.CursorX); - float cursorY = API.GetControlNormal(0, (int)Control.CursorY); -#elif ALTV - float cursorX = Alt.Natives.GetControlNormal(0, (int)Control.CursorX); - float cursorY = Alt.Natives.GetControlNormal(0, (int)Control.CursorY); -#elif RAGEMP - float cursorX = Invoker.Invoke(Natives.GetControlNormal, 0, (int)Control.CursorX); - float cursorY = Invoker.Invoke(Natives.GetControlNormal, 0, (int)Control.CursorY); -#elif RPH - float cursorX = NativeFunction.CallByHash(0xEC3C9B8D5327B563, 0, (int)Control.CursorX); - float cursorY = NativeFunction.CallByHash(0xEC3C9B8D5327B563, 0, (int)Control.CursorY); -#elif SHVDN3 || SHVDNC - float cursorX = Function.Call(Hash.GET_CONTROL_NORMAL, 0, (int)Control.CursorX); - float cursorY = Function.Call(Hash.GET_CONTROL_NORMAL, 0, (int)Control.CursorY); -#endif - return new PointF(cursorX, cursorY); - } - } + public static PointF CursorPositionRelative => GameScreen.Cursor.ToRelative(); #endregion @@ -105,11 +58,8 @@ public static PointF CursorPositionRelative /// The value of Y scaled to 1080p. public static void ToAbsolute(float relativeX, float relativeY, out float absoluteX, out float absoluteY) { - // Get the real width based on the aspect ratio - float width = 1080f * AspectRatio; - // And save the correct values - absoluteX = width * relativeX; - absoluteY = 1080f * relativeY; + absoluteX = relativeX.ToXScaled(); + absoluteY = relativeY.ToYScaled(); } /// /// Converts a 1080p-based resolution into relative values. @@ -120,11 +70,8 @@ public static void ToAbsolute(float relativeX, float relativeY, out float absolu /// The value of Y converted to relative. public static void ToRelative(float absoluteX, float absoluteY, out float relativeX, out float relativeY) { - // Get the real width based on the aspect ratio - float width = 1080f * AspectRatio; - // And save the correct values - relativeX = absoluteX / width; - relativeY = absoluteY / 1080f; + relativeX = absoluteX.ToXRelative(); + relativeY = absoluteY.ToYRelative(); } /// /// Checks if the cursor is inside of the specified area. @@ -149,16 +96,9 @@ public static void ToRelative(float absoluteX, float absoluteY, out float relati /// if the cursor is in the specified bounds, otherwise. public static bool IsCursorInArea(float x, float y, float width, float height) { - PointF cursor = CursorPositionRelative; - - ToRelative(width, height, out float realWidth, out float realHeight); - + // intentionally kept this way to avoid breaking backwards compatibility PointF realPos = GetRealPosition(x, y).ToRelative(); - - bool isX = cursor.X >= realPos.X && cursor.X <= realPos.X + realWidth; - bool isY = cursor.Y > realPos.Y && cursor.Y < realPos.Y + realHeight; - - return isX && isY; + return GameScreen.IsCursorInArea(realPos.X, realPos.Y, width, height); } /// /// Converts the specified position into one that is aware of . @@ -172,128 +112,27 @@ public static bool IsCursorInArea(float x, float y, float width, float height) /// The 1080p based X position. /// The 1080p based Y position. /// A new 1080p based position that is aware of the the Alignment. - public static PointF GetRealPosition(float x, float y) - { - // Convert the resolution to relative - ToRelative(x, y, out float relativeX, out float relativeY); - // Request the real location of the position - float realX = 0, realY = 0; -#if FIVEM - API.GetScriptGfxPosition(relativeX, relativeY, ref realX, ref realY); -#elif ALTV - Alt.Natives.GetScriptGfxAlignPosition(relativeX, relativeY, ref realX, ref realY); -#elif RAGEMP - FloatReference argX = new FloatReference(); - FloatReference argY = new FloatReference(); - Invoker.Invoke(0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY); - realX = argX.Value; - realY = argY.Value; -#elif RPH - using (NativePointer argX = new NativePointer(4)) - using (NativePointer argY = new NativePointer(4)) - { - NativeFunction.CallByHash(0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY); - realX = argX.GetValue(); - realY = argY.GetValue(); - } -#elif SHVDN3 || SHVDNC - using (OutputArgument argX = new OutputArgument()) - using (OutputArgument argY = new OutputArgument()) - { - Function.Call((Hash)0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY); // _GET_SCRIPT_GFX_POSITION - realX = argX.GetResult(); - realY = argY.GetResult(); - } -#endif - // And return it converted to absolute - ToAbsolute(realX, realY, out float absoluteX, out float absoluteY); - return new PointF(absoluteX, absoluteY); - } + public static PointF GetRealPosition(float x, float y) => SafeZone.GetSafePosition(x, y); /// /// Shows the cursor during the current game frame. /// - public static void ShowCursorThisFrame() - { -#if FIVEM - API.SetMouseCursorActiveThisFrame(); -#elif ALTV - Alt.Natives.SetMouseCursorThisFrame(); -#elif RAGEMP - Invoker.Invoke(0xAAE7CE1D63167423); -#elif RPH - NativeFunction.CallByHash(0xAAE7CE1D63167423); -#elif SHVDN3 || SHVDNC - Function.Call(Hash.SET_MOUSE_CURSOR_THIS_FRAME); -#endif - } + public static void ShowCursorThisFrame() => GameScreen.ShowCursorThisFrame(); /// /// Sets the alignment of game elements like , and . /// /// The Horizontal alignment of the items. /// The vertical alignment of the items. - public static void SetElementAlignment(Alignment horizontal, GFXAlignment vertical) - { - // If the enum value is not correct, raise an exception - if (!Enum.IsDefined(typeof(Alignment), horizontal)) - { - throw new ArgumentException("Alignment is not one of the allowed values (Left, Right, Center).", nameof(horizontal)); - } - - // Otherwise, just call the correct function - switch (horizontal) - { - case Alignment.Left: - SetElementAlignment(GFXAlignment.Left, vertical); - break; - case Alignment.Right: - SetElementAlignment(GFXAlignment.Right, vertical); - break; - case Alignment.Center: - SetElementAlignment(GFXAlignment.Right, vertical); - break; - } - } + public static void SetElementAlignment(Alignment horizontal, GFXAlignment vertical) => SafeZone.SetAlignment(horizontal, vertical); /// /// Sets the alignment of game elements like , and . /// /// The Horizontal alignment of the items. /// The vertical alignment of the items. - public static void SetElementAlignment(GFXAlignment horizontal, GFXAlignment vertical) - { -#if FIVEM - API.SetScriptGfxAlign((int)horizontal, (int)vertical); - API.SetScriptGfxAlignParams(0, 0, 0, 0); -#elif ALTV - Alt.Natives.SetScriptGfxAlign((int)horizontal, (int)vertical); - Alt.Natives.SetScriptGfxAlignParams(0, 0, 0, 0); -#elif RAGEMP - Invoker.Invoke(0xB8A850F20A067EB6, (int)horizontal, (int)vertical); - Invoker.Invoke(0xF5A2C681787E579D, 0, 0, 0, 0); -#elif RPH - NativeFunction.CallByHash(0xB8A850F20A067EB6, (int)horizontal, (int)vertical); - NativeFunction.CallByHash(0xF5A2C681787E579D, 0, 0, 0, 0); -#elif SHVDN3 || SHVDNC - Function.Call(Hash.SET_SCRIPT_GFX_ALIGN, (int)horizontal, (int)vertical); - Function.Call(Hash.SET_SCRIPT_GFX_ALIGN_PARAMS, 0, 0, 0, 0); -#endif - } + public static void SetElementAlignment(GFXAlignment horizontal, GFXAlignment vertical) => SafeZone.SetAlignment(horizontal, vertical); /// /// Resets the alignment of the game elements. /// - public static void ResetElementAlignment() - { -#if FIVEM - API.ResetScriptGfxAlign(); -#elif ALTV - Alt.Natives.ResetScriptGfxAlign(); -#elif RAGEMP - Invoker.Invoke(0xE3A3DB414A373DAB); -#elif RPH - NativeFunction.CallByHash(0xE3A3DB414A373DAB); -#elif SHVDN3 || SHVDNC - Function.Call(Hash.RESET_SCRIPT_GFX_ALIGN); -#endif - } + public static void ResetElementAlignment() => SafeZone.ResetAlignment(); #endregion } diff --git a/LemonUI/TimerBars/TimerBarCollection.cs b/LemonUI/TimerBars/TimerBarCollection.cs index e5b88107..cb7de5df 100644 --- a/LemonUI/TimerBars/TimerBarCollection.cs +++ b/LemonUI/TimerBars/TimerBarCollection.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Drawing; using LemonUI.Elements; +using LemonUI.Tools; namespace LemonUI.TimerBars { @@ -139,9 +140,7 @@ public void Remove(Func func) /// public void Recalculate() { - Screen.SetElementAlignment(GFXAlignment.Right, GFXAlignment.Bottom); - PointF pos = Screen.GetRealPosition(PointF.Empty); - Screen.ResetElementAlignment(); + PointF pos = SafeZone.BottomRight; pos.X += offset.X; pos.Y += offset.Y; diff --git a/LemonUI/Tools/Extensions.cs b/LemonUI/Tools/Extensions.cs new file mode 100644 index 00000000..4d9c7e88 --- /dev/null +++ b/LemonUI/Tools/Extensions.cs @@ -0,0 +1,73 @@ +using System.Drawing; + +namespace LemonUI.Tools +{ + /// + /// Extensions for converting values between relative and scaled. + /// + public static class Extensions + { + #region Float + + /// + /// Converts the scaled X or Width to a relative one. + /// + /// The value to convert. + /// A relative float between 0 and 1. + public static float ToXRelative(this float x) => x / (1080f * GameScreen.AspectRatio); + /// + /// Converts the scaled Y or Height to a relative one. + /// + /// The value to convert. + /// A relative float between 0 and 1. + public static float ToYRelative(this float y) => y / 1080f; + /// + /// Converts the relative X or Width float to a scaled one. + /// + /// The float to convert. + /// A scaled float. + public static float ToXScaled(this float x) => (1080f * GameScreen.AspectRatio) * x; + /// + /// Converts the relative Y or Height float to a scaled one. + /// + /// The float to convert. + /// A scaled float. + public static float ToYScaled(this float y) => 1080f * y; + + #endregion + + #region PointF + + /// + /// Converts a scaled 1080p-based position into a relative one. + /// + /// The scaled PointF. + /// A new PointF with relative values. + public static PointF ToRelative(this PointF point) => new PointF(point.X.ToXRelative(), point.Y.ToYRelative()); + /// + /// Converts a relative 0-1 position into a scaled one. + /// + /// The relative PointF. + /// A new PointF with scaled values. + public static PointF ToScaled(this PointF point) => new PointF(point.X.ToXScaled(), point.Y.ToYScaled()); + + #endregion + + #region SizeF + + /// + /// Converts a scaled 1080p-based position into a relative one. + /// + /// The scaled SizeF. + /// A new SizeF with relative values. + public static SizeF ToRelative(this SizeF size) => new SizeF(size.Width.ToXRelative(), size.Height.ToYRelative()); + /// + /// Converts a relative 0-1 position into a scaled one. + /// + /// The relative SizeF. + /// A new SizeF with scaled values. + public static SizeF ToScaled(this SizeF size) => new SizeF(size.Width.ToXScaled(), size.Height.ToYScaled()); + + #endregion + } +} diff --git a/LemonUI/Tools/GameScreen.cs b/LemonUI/Tools/GameScreen.cs new file mode 100644 index 00000000..4315cf57 --- /dev/null +++ b/LemonUI/Tools/GameScreen.cs @@ -0,0 +1,147 @@ +#if FIVEM +using CitizenFX.Core; +using CitizenFX.Core.Native; +using CitizenFX.Core.UI; +#elif RAGEMP +using RAGE.Game; +using RAGE.NUI; +#elif RPH +using Rage; +using Rage.Native; +using Control = Rage.GameControl; +#elif SHVDN3 || SHVDNC +using GTA; +using GTA.Native; +#elif ALTV +using AltV.Net.Client; +using LemonUI.Elements; +#endif +using System.Drawing; + +namespace LemonUI.Tools +{ + /// + /// The screen of the game being rendered. + /// + public static class GameScreen + { + #region Properties + + /// + /// Gets the actual Screen resolution the game is being rendered at. + /// + public static SizeF AbsoluteResolution + { + get + { +#if ALTV + int height = 0, width = 0; + Alt.Natives.GetActualScreenResolution(ref width, ref height); + return new SizeF(width, height); +#elif FIVEM + return CitizenFX.Core.UI.Screen.Resolution; +#elif RAGEMP + ScreenResolutionType raw = Game.ScreenResolution; + return new SizeF(raw.Width, raw.Height); +#elif RPH + return Game.Resolution; +#elif SHVDN3 || SHVDNC + return GTA.UI.Screen.Resolution; +#endif + } + } + /// + /// The Aspect Ratio of the screen. + /// + public static float AspectRatio + { + get + { +#if FIVEM + return API.GetAspectRatio(false); +#elif RAGEMP + return Invoker.Invoke(Natives.GetAspectRatio); +#elif RPH + return NativeFunction.CallByHash(0xF1307EF624A80D87, false); +#elif SHVDN3 || SHVDNC + return Function.Call(Hash.GET_ASPECT_RATIO, false); +#elif ALTV + return Alt.Natives.GetAspectRatio(false); +#endif + } + } + /// + /// The location of the cursor on screen between 0 and 1. + /// + public static PointF Cursor + { + get + { +#if FIVEM + float cursorX = API.GetControlNormal(0, (int)Control.CursorX); + float cursorY = API.GetControlNormal(0, (int)Control.CursorY); +#elif ALTV + float cursorX = Alt.Natives.GetControlNormal(0, (int)Control.CursorX); + float cursorY = Alt.Natives.GetControlNormal(0, (int)Control.CursorY); +#elif RAGEMP + float cursorX = Invoker.Invoke(Natives.GetControlNormal, 0, (int)Control.CursorX); + float cursorY = Invoker.Invoke(Natives.GetControlNormal, 0, (int)Control.CursorY); +#elif RPH + float cursorX = NativeFunction.CallByHash(0xEC3C9B8D5327B563, 0, (int)Control.CursorX); + float cursorY = NativeFunction.CallByHash(0xEC3C9B8D5327B563, 0, (int)Control.CursorY); +#elif SHVDN3 || SHVDNC + float cursorX = Function.Call(Hash.GET_CONTROL_NORMAL, 0, (int)Control.CursorX); + float cursorY = Function.Call(Hash.GET_CONTROL_NORMAL, 0, (int)Control.CursorY); +#endif + return new PointF(cursorX.ToXScaled(), cursorY.ToYScaled()); + } + } + + #endregion + + #region Functions + + /// + /// Checks if the cursor is inside of the scaled area. + /// + /// The scaled position. + /// The scaled size of the area. + /// if the cursor is in the specified bounds, otherwise. + public static bool IsCursorInArea(PointF pos, SizeF size) => IsCursorInArea(pos.X, pos.Y, size.Width, size.Height); + /// + /// Checks if the cursor is inside of the scaled area. + /// + /// The scaled X position. + /// The scaled Y position. + /// The scaled width of the area. + /// The scaled height of the area. + /// if the cursor is in the specified bounds, otherwise. + public static bool IsCursorInArea(float x, float y, float width, float height) + { + PointF cursorPosition = Cursor; + + bool isX = cursorPosition.X >= x && cursorPosition.X <= x + width; + bool isY = cursorPosition.Y > y && cursorPosition.Y < y + height; + return isX && isY; + } + /// + /// Shows the cursor during the current game frame. + /// + public static void ShowCursorThisFrame() + { +#if FIVEM + API.SetMouseCursorActiveThisFrame(); +#elif ALTV + Alt.Natives.SetMouseCursorThisFrame(); +#elif RAGEMP + Invoker.Invoke(0xAAE7CE1D63167423); +#elif RPH + NativeFunction.CallByHash(0xAAE7CE1D63167423); +#elif SHVDN3 || SHVDNC + Function.Call(Hash.SET_MOUSE_CURSOR_THIS_FRAME); +#endif + } + + #endregion + } +} diff --git a/LemonUI/Tools/SafeZone.cs b/LemonUI/Tools/SafeZone.cs new file mode 100644 index 00000000..4ddfa8d3 --- /dev/null +++ b/LemonUI/Tools/SafeZone.cs @@ -0,0 +1,209 @@ +#if ALTV +using AltV.Net.Client; +#elif RAGEMP +using RAGE.Game; +#elif FIVEM +using CitizenFX.Core.Native; +using CitizenFX.Core.UI; +#elif RPH +using Rage.Native; +#elif SHVDN3 || SHVDNC +using GTA.Native; +using GTA.UI; +#endif +using System; +using System.Drawing; + +namespace LemonUI.Tools +{ + /// + /// Tools for changing, resetting and retrieving the Safe Zone of the game. + /// + public static class SafeZone + { + #region Properties + + /// + /// The size of the safe zone. + /// + /// + /// This property should not be used to manually calculate the safe zone. Use to get the safe zone size. + /// + public static float Size + { + get + { +#if ALTV + return Alt.Natives.GetSafeZoneSize(); +#elif FIVEM + return API.GetSafeZoneSize(); +#elif RAGEMP + return Invoker.Invoke(Natives.GetSafeZoneSize); +#elif RPH + return NativeFunction.CallByHash(0xBAF107B6BB2C97F0); +#elif SHVDN3 || SHVDNC + return Function.Call(Hash.GET_SAFE_ZONE_SIZE); +#endif + } + } + /// + /// The top left corner after the safe zone. + /// + public static PointF TopLeft => GetPositionAt(PointF.Empty, GFXAlignment.Left, GFXAlignment.Top); + /// + /// The top right corner after the safe zone. + /// + public static PointF TopRight => GetPositionAt(PointF.Empty, GFXAlignment.Right, GFXAlignment.Top); + /// + /// The bottom left corner after the safe zone. + /// + public static PointF BottomLeft => GetPositionAt(PointF.Empty, GFXAlignment.Left, GFXAlignment.Bottom); + /// + /// The bottom right corner after the safe zone. + /// + public static PointF BottomRight => GetPositionAt(PointF.Empty, GFXAlignment.Right, GFXAlignment.Bottom); + + #endregion + + #region Tools + + private static GFXAlignment AlignmentToGFXAlignment(Alignment alignment) + { + switch (alignment) + { + case Alignment.Left: + return GFXAlignment.Left; + case Alignment.Right: + return GFXAlignment.Right; + case Alignment.Center: + return GFXAlignment.Center; + default: + throw new ArgumentException("Alignment is not one of the allowed values (Left, Right, Center).", nameof(alignment)); + } + } + + #endregion + + #region Functions + + /// + /// Converts the specified position into one that is aware of the safe zone. + /// + /// The original 1080p based position. + /// A new 1080p based position that is aware of the the Alignment. + public static PointF GetSafePosition(PointF og) => GetSafePosition(og.X, og.Y); + /// + /// Converts the specified position into one that is aware of . + /// + /// The 1080p based X position. + /// The 1080p based Y position. + /// A new 1080p based position that is aware of the the Alignment. + public static PointF GetSafePosition(float x, float y) + { + float relativeX = x.ToXRelative(); + float relativeY = y.ToYRelative(); + + float realX = 0, realY = 0; +#if FIVEM + API.GetScriptGfxPosition(relativeX, relativeY, ref realX, ref realY); +#elif ALTV + Alt.Natives.GetScriptGfxAlignPosition(relativeX, relativeY, ref realX, ref realY); +#elif RAGEMP + FloatReference argX = new FloatReference(); + FloatReference argY = new FloatReference(); + Invoker.Invoke(0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY); + realX = argX.Value; + realY = argY.Value; +#elif RPH + using (NativePointer argX = new NativePointer(4)) + using (NativePointer argY = new NativePointer(4)) + { + NativeFunction.CallByHash(0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY); + realX = argX.GetValue(); + realY = argY.GetValue(); + } +#elif SHVDN3 || SHVDNC + using (OutputArgument argX = new OutputArgument()) + using (OutputArgument argY = new OutputArgument()) + { + Function.Call((Hash)0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY); // _GET_SCRIPT_GFX_POSITION + realX = argX.GetResult(); + realY = argY.GetResult(); + } +#endif + + return new PointF(realX.ToXScaled(), realY.ToYScaled()); + } + /// + /// Sets the alignment for the safe zone. + /// + /// The Horizontal alignment of the items. + /// The vertical alignment of the items. + public static void SetAlignment(Alignment horizontal, GFXAlignment vertical) => SetAlignment(AlignmentToGFXAlignment(horizontal), vertical); + /// + /// Sets the alignment for the safe zone. + /// + /// The Horizontal alignment of the items. + /// The vertical alignment of the items. + public static void SetAlignment(GFXAlignment horizontal, GFXAlignment vertical) + { +#if FIVEM + API.SetScriptGfxAlign((int)horizontal, (int)vertical); + API.SetScriptGfxAlignParams(0, 0, 0, 0); +#elif ALTV + Alt.Natives.SetScriptGfxAlign((int)horizontal, (int)vertical); + Alt.Natives.SetScriptGfxAlignParams(0, 0, 0, 0); +#elif RAGEMP + Invoker.Invoke(0xB8A850F20A067EB6, (int)horizontal, (int)vertical); + Invoker.Invoke(0xF5A2C681787E579D, 0, 0, 0, 0); +#elif RPH + NativeFunction.CallByHash(0xB8A850F20A067EB6, (int)horizontal, (int)vertical); + NativeFunction.CallByHash(0xF5A2C681787E579D, 0, 0, 0, 0); +#elif SHVDN3 || SHVDNC + Function.Call(Hash.SET_SCRIPT_GFX_ALIGN, (int)horizontal, (int)vertical); + Function.Call(Hash.SET_SCRIPT_GFX_ALIGN_PARAMS, 0, 0, 0, 0); +#endif + } + /// + /// Resets the alignment of the safe zone. + /// + public static void ResetAlignment() + { +#if FIVEM + API.ResetScriptGfxAlign(); +#elif ALTV + Alt.Natives.ResetScriptGfxAlign(); +#elif RAGEMP + Invoker.Invoke(0xE3A3DB414A373DAB); +#elif RPH + NativeFunction.CallByHash(0xE3A3DB414A373DAB); +#elif SHVDN3 || SHVDNC + Function.Call(Hash.RESET_SCRIPT_GFX_ALIGN); +#endif + } + /// + /// Gets the specified position with the specified safe zone alignment. + /// + /// The position to get. + /// The horizontal alignment. + /// The vertical alignment. + /// The safe zone alignment. + public static PointF GetPositionAt(PointF position, Alignment horizontal, GFXAlignment vertical) => GetPositionAt(position, AlignmentToGFXAlignment(horizontal), vertical); + /// + /// Gets the specified position with the specified safe zone alignment. + /// + /// The position to get. + /// The horizontal alignment. + /// The vertical alignment. + /// The scaled safe zone alignment. + public static PointF GetPositionAt(PointF position, GFXAlignment horizontal, GFXAlignment vertical) + { + SetAlignment(horizontal, vertical); + PointF pos = GetSafePosition(position); + ResetAlignment(); + return pos; + } + + #endregion + } +} diff --git a/README.md b/README.md index d41a970c..fd16fca9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# LemonUI
[![GitHub Actions][actions-img]][actions-url] [![NuGet][nuget-img-2]][nuget-url-2] [![NuGet][nuget-img-3]][nuget-url-3] [![NuGet][nuget-img-f]][nuget-url-f] [![NuGet][nuget-img-r]][nuget-url-r] [![Patreon][patreon-img]][patreon-url] [![PayPal][paypal-img]][paypal-url] [![Discord][discord-img]][discord-url] +# LemonUI
[![GitHub Actions][actions-img]][actions-url] [![NuGet][nuget-img-3]][nuget-url-3] [![NuGet][nuget-img-f]][nuget-url-f] [![NuGet][nuget-img-r]][nuget-url-r] [![NuGet][nuget-img-a]][nuget-url-a] [![Patreon][patreon-img]][patreon-url] [![PayPal][paypal-img]][paypal-url] [![Discord][discord-img]][discord-url] LemonUI is a framework for creating UI systems in Grand Theft Auto V that is compatible with FiveM, RageMP, RagePluginHook ScriptHookVDotNet 2 and ScriptHookVDotNet 3. It allows you to create UI Elements with a NativeUI-like style, or you can also create your own UI System from scratch via the resolution-independant classes for Text, Rectangles and Textures. @@ -37,29 +37,23 @@ Copy all of the files from the **RPH** folder inside of the compressed file to t ### ScriptHookVDotNet 2 and ScriptHookVDotNet 3 -**PLEASE NOTE THAT THE LAST VERSION THAT SUPPORTED SHVDN2 WAS 1.5.2. [You can download it here]().** +**PLEASE NOTE THAT THE LAST VERSION THAT SUPPORTED SHVDN2 WAS 1.5.2. [You can download it here](https://github.com/LemonUIbyLemon/LemonUI/releases/tag/v1.5.2).** Copy all of the files from the **SHVDN2** and/or **SHVDN3** folder(s) inside of the compressed file to your **scripts** directory. -### Developers - -Add any of the NuGet packages linked above and start working in your IDE. - -If you are using RagePluginHook or ScriptHookVDotNet, you can disable the copy of the dll in your IDE so your users always have to use the latest version available. - -If you are using RageMP, you will need to download the latest release and copy **LemonUI.RageMP.cs** to you client solution. - ## Usage Once installed, the mods that require LemonUI will start working. -If you are a developer, check the [wiki](https://github.com/LemonUIbyLemon/LemonUI/wiki) for information to implement LemonUI in your mod. +## Developers + +Check the [Quick Start guide](https://github.com/LemonUIbyLemon/LemonUI/wiki/Quick-Start) to learn how to use LemonUI in your project. + +If you would like to make changes to the code, clone the repo, restore the packages and open the solution in your favorite IDE. [actions-img]: https://img.shields.io/github/actions/workflow/status/LemonUIbyLemon/LemonUI/main.yml?branch=master&label=actions [actions-url]: https://github.com/LemonUIbyLemon/LemonUI/actions -[nuget-img-2]: https://img.shields.io/nuget/v/LemonUI.SHVDN2?label=nuget%20%28shvdn%202%29 -[nuget-url-2]: https://www.nuget.org/packages/LemonUI.SHVDN2/ -[nuget-img-3]: https://img.shields.io/nuget/v/LemonUI.SHVDN3?label=nuget%20%28shvdn%203%29 +[nuget-img-3]: https://img.shields.io/nuget/v/LemonUI.SHVDN3?label=nuget%20%28shvdn3%29 [nuget-url-3]: https://www.nuget.org/packages/LemonUI.SHVDN3/ [nuget-img-f]: https://img.shields.io/nuget/v/LemonUI.FiveM?label=nuget%20%28fivem%29 [nuget-url-f]: https://www.nuget.org/packages/LemonUI.FiveM/ @@ -67,6 +61,8 @@ If you are a developer, check the [wiki](https://github.com/LemonUIbyLemon/Lemon [nuget-url-m]: https://www.nuget.org/packages/LemonUI.RageMP/ [nuget-img-r]: https://img.shields.io/nuget/v/LemonUI.RagePluginHook?label=nuget%20%28rph%29 [nuget-url-r]: https://www.nuget.org/packages/LemonUI.RagePluginHook/ +[nuget-img-a]: https://img.shields.io/nuget/v/LemonUI.AltV?label=nuget%20%28altv%29 +[nuget-url-a]: https://www.nuget.org/packages/LemonUI.AltV/ [patreon-img]: https://img.shields.io/badge/support-patreon-FF424D.svg [patreon-url]: https://www.patreon.com/lemonchan [paypal-img]: https://img.shields.io/badge/support-paypal-0079C1.svg