Skip to content
44 changes: 44 additions & 0 deletions EXILED/Exiled.Events/EventArgs/Player/RoomChangedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// -----------------------------------------------------------------------
// <copyright file="RoomChangedEventArgs.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------
namespace Exiled.Events.EventArgs.Player
{
using Exiled.API.Features;
using Exiled.Events.EventArgs.Interfaces;
using MapGeneration;

/// <summary>
/// Contains the information when a player changes rooms.
/// </summary>
public class RoomChangedEventArgs : IPlayerEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="RoomChangedEventArgs"/> class.
/// </summary>
/// <param name="player">The player whose room has changed.</param>
/// <param name="oldRoom">The room identifier before the change (Can be null on round start).</param>
/// <param name="newRoom">The room identifier after the change.</param>
public RoomChangedEventArgs(ReferenceHub player, RoomIdentifier oldRoom, RoomIdentifier newRoom)
{
Player = Player.Get(player);
OldRoom = Room.Get(oldRoom);
NewRoom = Room.Get(newRoom);
}

/// <inheritdoc/>
public Player Player { get; }

/// <summary>
/// Gets the previous room the player was in.
/// </summary>
public Room OldRoom { get; }

/// <summary>
/// Gets the new room the player entered.
/// </summary>
public Room NewRoom { get; }
}
}
11 changes: 11 additions & 0 deletions EXILED/Exiled.Events/Handlers/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,11 @@ public class Player
/// </summary>
public static Event<ChangingSpectatedPlayerEventArgs> ChangingSpectatedPlayer { get; set; } = new();

/// <summary>
/// Invoked when a <see cref="API.Features.Player"/> changes rooms.
/// </summary>
public static Event<RoomChangedEventArgs> RoomChanged { get; set; } = new();

/// <summary>
/// Invoked before a <see cref="API.Features.Player"/> toggles the NoClip mode.
/// </summary>
Expand Down Expand Up @@ -799,6 +804,12 @@ public class Player
/// <param name="ev">The <see cref="RemovedHandcuffsEventArgs"/> instance.</param>
public static void OnRemovedHandcuffs(RemovedHandcuffsEventArgs ev) => RemovedHandcuffs.InvokeSafely(ev);

/// <summary>
/// Called when a <see cref="API.Features.Player"/> changes rooms.
/// </summary>
/// <param name="ev">The <see cref="RoomChangedEventArgs"/> instance.</param>
public static void OnRoomChanged(RoomChangedEventArgs ev) => RoomChanged.InvokeSafely(ev);

/// <summary>
/// Called before a <see cref="API.Features.Player"/> escapes.
/// </summary>
Expand Down
91 changes: 91 additions & 0 deletions EXILED/Exiled.Events/Patches/Events/Player/ChangedRoom.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// -----------------------------------------------------------------------
// <copyright file="ChangedRoom.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

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;

/// <summary>
/// Patches <see cref="CurrentRoomPlayerCache.ValidateCache"/> to add the <see cref="Player.RoomChanged"/> event.
/// </summary>
[EventPatch(typeof(Player), nameof(Player.RoomChanged))]
[HarmonyPatch(typeof(CurrentRoomPlayerCache), nameof(CurrentRoomPlayerCache.ValidateCache))]
internal class ChangedRoom
{
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.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[]
{
// RoomIdentifier 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 = lastDetected
new(OpCodes.Ldloc_1),
new(OpCodes.Dup),
new(OpCodes.Stloc_S, newRoom),

// oldRoom
new(OpCodes.Ldloc_S, oldRoom),

// if (oldRoom == newRoom) return;
new(OpCodes.Call, Method(typeof(object), nameof(object.ReferenceEquals), new[] { typeof(object), typeof(object) })),
new(OpCodes.Brtrue_S, returnLabel),

// this._roleManager.gameObject.GetComponent<ReferenceHub>();
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),

// RoomChangedEventArgs ev = new RoomChangedEventArgs(hub, oldRoom, newRoom);
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RoomChangedEventArgs))[0]),

// Handlers.Player.OnRoomChanged(ev);
new(OpCodes.Call, Method(typeof(Player), nameof(Player.OnRoomChanged))),
});

for (int i = 0; i < newInstructions.Count; i++)
yield return newInstructions[i];

ListPool<CodeInstruction>.Pool.Return(newInstructions);
}
}
}