diff --git a/EXILED/Exiled.Events/EventArgs/Player/RoomChangedEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/RoomChangedEventArgs.cs new file mode 100644 index 0000000000..57f17a3eda --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/RoomChangedEventArgs.cs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.Events.EventArgs.Player +{ + using Exiled.API.Features; + using Exiled.Events.EventArgs.Interfaces; + using MapGeneration; + + /// + /// Contains the information when a player changes rooms. + /// + public class RoomChangedEventArgs : IPlayerEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The player whose room has changed. + /// The room identifier before the change (Can be null on round start). + /// The room identifier after the change. + public RoomChangedEventArgs(ReferenceHub player, RoomIdentifier oldRoom, RoomIdentifier newRoom) + { + Player = Player.Get(player); + OldRoom = Room.Get(oldRoom); + NewRoom = Room.Get(newRoom); + } + + /// + public Player Player { get; } + + /// + /// Gets the previous room the player was in. + /// + public Room OldRoom { get; } + + /// + /// Gets the new room the player entered. + /// + public Room NewRoom { get; } + } +} diff --git a/EXILED/Exiled.Events/EventArgs/Player/ZoneChangedEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ZoneChangedEventArgs.cs new file mode 100644 index 0000000000..7d8c0c714d --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/ZoneChangedEventArgs.cs @@ -0,0 +1,58 @@ +// ----------------------------------------------------------------------- +// +// 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.Features; + using Exiled.Events.EventArgs.Interfaces; + using MapGeneration; + + /// + /// Contains the information when a player changes zones. + /// + public class ZoneChangedEventArgs : IPlayerEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The player whose zone has changed. + /// The previous room the player was in. + /// The new room the player entered. + public ZoneChangedEventArgs(ReferenceHub player, RoomIdentifier oldRoom, RoomIdentifier newRoom) + { + Player = Player.Get(player); + OldRoom = Room.Get(oldRoom); + NewRoom = Room.Get(newRoom); + OldZone = OldRoom.Zone; + NewZone = NewRoom.Zone; + } + + /// + public Player Player { get; } + + /// + /// Gets the previous zone the player was in. + /// + public ZoneType OldZone { get; } + + /// + /// Gets the new zone the player entered. + /// + public ZoneType NewZone { get; } + + /// + /// Gets the previous room the player was in. + /// + public Room OldRoom { get; } + + /// + /// Gets the new room the player entered. + /// + public Room NewRoom { get; } + } +} diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index e35df7b044..b0657a2d62 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -489,6 +489,16 @@ public class Player /// public static Event ChangingSpectatedPlayer { get; set; } = new(); + /// + /// Invoked when a changes rooms. + /// + public static Event RoomChanged { get; set; } = new(); + + /// + /// Invoked when a changes zones. + /// + public static Event ZoneChanged { get; set; } = new(); + /// /// Invoked before a toggles the NoClip mode. /// @@ -799,6 +809,18 @@ public class Player /// The instance. public static void OnRemovedHandcuffs(RemovedHandcuffsEventArgs ev) => RemovedHandcuffs.InvokeSafely(ev); + /// + /// Called when a changes rooms. + /// + /// The instance. + public static void OnRoomChanged(RoomChangedEventArgs ev) => RoomChanged.InvokeSafely(ev); + + /// + /// Called when a changes zones. + /// + /// The instance. + public static void OnZoneChanged(ZoneChangedEventArgs ev) => ZoneChanged.InvokeSafely(ev); + /// /// Called before a escapes. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoomZone.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoomZone.cs new file mode 100644 index 0000000000..545f52cef7 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangedRoomZone.cs @@ -0,0 +1,117 @@ +// ----------------------------------------------------------------------- +// +// 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 Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Player; + using Exiled.Events.Handlers; + using HarmonyLib; + using MapGeneration; + using UnityEngine; + + using static HarmonyLib.AccessTools; + + /// + /// Patches to add the and events. + /// + [EventPatch(typeof(Player), nameof(Player.RoomChanged))] + [EventPatch(typeof(Player), nameof(Player.ZoneChanged))] + [HarmonyPatch(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache.ValidateCache))] + internal class ChangedRoomZone + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label returnLabel = generator.DefineLabel(); + + LocalBuilder oldRoom = generator.DeclareLocal(typeof(RoomIdentifier)); + LocalBuilder newRoom = generator.DeclareLocal(typeof(RoomIdentifier)); + + int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Ldloca_S); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // oldRoom = this._lastDetected + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache._lastDetected))), + new(OpCodes.Stloc_S, oldRoom), + }); + + int lastIndex = newInstructions.Count - 1; + + newInstructions[lastIndex].WithLabels(returnLabel); + + newInstructions.InsertRange(lastIndex, new CodeInstruction[] + { + // newRoom = this._lastDetected + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache._lastDetected))), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, newRoom), + + new(OpCodes.Ldloc_S, oldRoom), + + // if (oldRoom == newRoom) return; + new(OpCodes.Call, Method(typeof(object), nameof(object.Equals), new[] { typeof(object), typeof(object) })), + new(OpCodes.Brtrue_S, returnLabel), + + // ReferenceHub hub = this._roleManager.gameObject.GetComponent(); + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache._roleManager))), + new(OpCodes.Call, Method(typeof(Component), nameof(Component.GetComponent)).MakeGenericMethod(typeof(ReferenceHub))), + + // oldRoom + new(OpCodes.Ldloc_S, oldRoom), + + // newRoom + new(OpCodes.Ldloc_S, newRoom), + + // Handlers.Player.OnRoomChanged(new RoomChangedEventArgs(hub, oldRoom, newRoom)); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RoomChangedEventArgs))[0]), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.OnRoomChanged))), + + // oldRoom.Zone + new(OpCodes.Ldloc_S, oldRoom), + new(OpCodes.Ldfld, Field(typeof(RoomIdentifier), nameof(RoomIdentifier.Zone))), + + // newRoom.Zone + new(OpCodes.Ldloc_S, newRoom), + new(OpCodes.Ldfld, Field(typeof(RoomIdentifier), nameof(RoomIdentifier.Zone))), + + // if (oldRoom.Zone == newRoom.Zone) return; + new(OpCodes.Ceq), + new(OpCodes.Brtrue_S, returnLabel), + + // ReferenceHub hub = this._roleManager.gameObject.GetComponent(); + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache._roleManager))), + new(OpCodes.Call, Method(typeof(Component), nameof(Component.GetComponent)).MakeGenericMethod(typeof(ReferenceHub))), + + // oldRoom + new(OpCodes.Ldloc_S, oldRoom), + + // newRoom + new(OpCodes.Ldloc_S, newRoom), + + // Handlers.Player.OnZoneChanged(new ZoneChangedEventArgs(hub, oldRoom, newRoom)); + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ZoneChangedEventArgs))[0]), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.OnZoneChanged))), + }); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } +}