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);
+ }
+ }
+}