diff --git a/EXILED/Exiled.API/Enums/AspectRatioType.cs b/EXILED/Exiled.API/Enums/AspectRatioType.cs new file mode 100644 index 0000000000..43158a59fb --- /dev/null +++ b/EXILED/Exiled.API/Enums/AspectRatioType.cs @@ -0,0 +1,55 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Enums +{ + /// + /// All available screen aspect ratio types. + /// + public enum AspectRatioType : byte + { + /// + /// Unknown aspect ratio. + /// + Unknown, + + /// + /// 3:2 aspect ratio (common in classic cameras and some tablets). + /// + Ratio3_2, + + /// + /// 4:3 aspect ratio (standard definition TVs, older monitors). + /// + Ratio4_3, + + /// + /// 5:4 aspect ratio (some older computer monitors). + /// + Ratio5_4, + + /// + /// 16:9 aspect ratio (modern widescreen displays, HDTV). + /// + Ratio16_9, + + /// + /// 16:10 aspect ratio (common in productivity monitors and laptops). + /// + Ratio16_10, + + /// + /// 21:9 aspect ratio (ultrawide displays). + /// + Ratio21_9, + + /// + /// 32:9 aspect ratio (super ultrawide displays). + /// + Ratio32_9, + } +} diff --git a/EXILED/Exiled.API/Extensions/FloatExtensions.cs b/EXILED/Exiled.API/Extensions/FloatExtensions.cs new file mode 100644 index 0000000000..ce51bb4889 --- /dev/null +++ b/EXILED/Exiled.API/Extensions/FloatExtensions.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Extensions +{ + using System; + using System.Collections.Generic; + + using Exiled.API.Enums; + + /// + /// A set of extensions for . + /// + public static class FloatExtensions + { + private static readonly Dictionary AspectRatioReferences = new() + { + { AspectRatioType.Ratio3_2, 3f / 2f }, + { AspectRatioType.Ratio4_3, 4f / 3f }, + { AspectRatioType.Ratio5_4, 5f / 4f }, + { AspectRatioType.Ratio16_9, 16f / 9f }, + { AspectRatioType.Ratio16_10, 16f / 10f }, + { AspectRatioType.Ratio21_9, 21f / 9f }, + { AspectRatioType.Ratio32_9, 32f / 9f }, + }; + + /// + /// Gets the closest for a given aspect ratio value. + /// + /// The aspect ratio value to compare. + /// The closest matching . + public static AspectRatioType GetAspectRatioLabel(this float ratio) + { + float closestDiff = float.MaxValue; + AspectRatioType closestRatio = AspectRatioType.Unknown; + + foreach (KeyValuePair kvp in AspectRatioReferences) + { + float diff = Math.Abs(ratio - kvp.Value); + if (diff < closestDiff) + { + closestDiff = diff; + closestRatio = kvp.Key; + } + } + + return closestRatio; + } + } +} diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index d24ec00757..cce1139393 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -343,6 +343,11 @@ public PlayerInfoArea InfoArea set => ReferenceHub.nicknameSync.Network_playerInfoToShow = value; } + /// + /// Gets the player's current aspect ratio type. + /// + public AspectRatioType AspectRatio => ReferenceHub.aspectRatioSync.AspectRatio.GetAspectRatioLabel(); + /// /// Gets or sets the player's custom player info string. This string is displayed along with the player's . /// diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangedRatioEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangedRatioEventArgs.cs new file mode 100644 index 0000000000..5df04b729e --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/ChangedRatioEventArgs.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Player +{ + using Exiled.API.Enums; + using Exiled.API.Extensions; + using Exiled.API.Features; + using Exiled.Events.EventArgs.Interfaces; + + /// + /// Contains all information after a player's Aspect Ratio changes. + /// + public class ChangedRatioEventArgs : IPlayerEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The player who is changed ratio. + /// + /// + /// Old aspect ratio of the player. + /// + /// + /// New aspect ratio of the player. + /// + /// + public ChangedRatioEventArgs(ReferenceHub player, float oldratio, float newratio) + { + Player = Player.Get(player); + OldRatio = oldratio.GetAspectRatioLabel(); + NewRatio = newratio.GetAspectRatioLabel(); + } + + /// + /// Gets the player who is changed ratio. + /// + public Player Player { get; } + + /// + /// Gets the players old ratio. + /// + public AspectRatioType OldRatio { get; } + + /// + /// Gets the players new ratio. + /// + public AspectRatioType NewRatio { get; } + } +} diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index e35df7b044..b444aa4b4f 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -103,6 +103,11 @@ public class Player /// public static Event CancelledItemUse { get; set; } = new(); + /// + /// Invoked after a 's aspect ratio has changed. + /// + public static Event ChangedRatio { get; set; } = new(); + /// /// Invoked after a interacted with something. /// @@ -684,6 +689,12 @@ public class Player /// The instance. public static void OnCancelledItemUse(CancelledItemUseEventArgs ev) => CancelledItemUse.InvokeSafely(ev); + /// + /// Called after a 's aspect ratio changes. + /// + /// The instance. + public static void OnChangedRatio(ChangedRatioEventArgs ev) => ChangedRatio.InvokeSafely(ev); + /// /// Called after a interacted with something. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs new file mode 100644 index 0000000000..8a12f60f94 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs @@ -0,0 +1,85 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Player +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using CentralAuth; + using Exiled.Events.EventArgs.Player; + using HarmonyLib; + using UnityEngine; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the event. + /// + [HarmonyPatch(typeof(AspectRatioSync), nameof(AspectRatioSync.UserCode_CmdSetAspectRatio__Single))] + internal class ChangedAspectRatio + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder oldratio = generator.DeclareLocal(typeof(float)); + LocalBuilder hub = generator.DeclareLocal(typeof(ReferenceHub)); + + Label retLabel = generator.DefineLabel(); + + newInstructions.InsertRange(0, new CodeInstruction[] + { + // float OldRatio = this.AspectRatio; + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(AspectRatioSync), nameof(AspectRatioSync.AspectRatio))), + new(OpCodes.Stloc_S, oldratio.LocalIndex), + }); + + int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ret); + newInstructions[index].WithLabels(retLabel); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // ReferenceHub hub = this.GetComponent(); + new(OpCodes.Ldarg_0), + new(OpCodes.Call, Method(typeof(Component), nameof(Component.GetComponent)).MakeGenericMethod(typeof(ReferenceHub))), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, hub.LocalIndex), + + // if (hub.authManager._targetInstanceMode != ClientInstanceMode.ReadyClient) return; + new(OpCodes.Ldfld, Field(typeof(ReferenceHub), nameof(ReferenceHub.authManager))), + new(OpCodes.Ldfld, Field(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager._targetInstanceMode))), + new(OpCodes.Ldc_I4, 1), + new(OpCodes.Bne_Un_S, retLabel), + + // hub + new(OpCodes.Ldloc, hub.LocalIndex), + + // OldRatio + new(OpCodes.Ldloc, oldratio.LocalIndex), + + // this.AspectRatio + new(OpCodes.Ldarg_0), + new(OpCodes.Call, PropertyGetter(typeof(AspectRatioSync), nameof(AspectRatioSync.AspectRatio))), + + // ChangedRatioEventArgs ev = new ChangedRatioEventArgs(ReferenceHub, float, float) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ChangedRatioEventArgs))[0]), + + // Handlers.Player.OnChangedRatio(ev); + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnChangedRatio))), + }); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } +}