Skip to content
51 changes: 51 additions & 0 deletions EXILED/Exiled.Events/EventArgs/Scp173/AddingObserverEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// -----------------------------------------------------------------------
// <copyright file="AddingObserverEventArgs.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.Scp173
{
using Exiled.API.Features;
using Exiled.API.Features.Roles;
using Interfaces;

/// <summary>
/// Contains all information before a player sees SCP-173.
/// </summary>
public class AddingObserverEventArgs : IScp173Event, IDeniableEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="AddingObserverEventArgs"/> class.
/// </summary>
/// <param name="player"><inheritdoc cref="Scp173" /></param>
/// <param name="observer"><inheritdoc cref="Player" /></param>
/// <param name="isAllowed"><inheritdoc cref="IsAllowed" /></param>
public AddingObserverEventArgs(Player player, Player observer, bool isAllowed = true)
{
Scp173 = player.Role.As<Scp173Role>();
Player = player;
Observer = observer;
IsAllowed = isAllowed;
}

/// <inheritdoc />
public Scp173Role Scp173 { get; }

/// <summary>
/// Gets the target who has looked at SCP-173.
/// </summary>
public Player Observer { get; }

/// <summary>
/// Gets the player who's controlling SCP-173.
/// </summary>
public Player Player { get; }

/// <summary>
/// Gets or sets a value indicating whether the player can be added as an observer.
/// </summary>
public bool IsAllowed { get; set; }
}
}
44 changes: 44 additions & 0 deletions EXILED/Exiled.Events/EventArgs/Scp173/RemovedObserverEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// -----------------------------------------------------------------------
// <copyright file="RemovedObserverEventArgs.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.Scp173
{
using Exiled.API.Features;
using Exiled.API.Features.Roles;
using Interfaces;

/// <summary>
/// Contains all information after a player stops looking at SCP-173.
/// </summary>
public class RemovedObserverEventArgs : IScp173Event
{
/// <summary>
/// Initializes a new instance of the <see cref="RemovedObserverEventArgs"/> class.
/// </summary>
/// <param name="player"><inheritdoc cref="Scp173" /></param>
/// <param name="observer"><inheritdoc cref="Player" /></param>
public RemovedObserverEventArgs(Player player, Player observer)
{
Scp173 = player.Role.As<Scp173Role>();
Player = player;
Observer = observer;
}

/// <inheritdoc />
public Scp173Role Scp173 { get; }

/// <summary>
/// Gets the player who's controlling SCP-173.
/// </summary>
public Player Player { get; }

/// <summary>
/// Gets the target who no longer sees SCP-173.
/// </summary>
public Player Observer { get; }
}
}
22 changes: 22 additions & 0 deletions EXILED/Exiled.Events/Handlers/Scp173.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ public static class Scp173
/// </summary>
public static Event<BeingObservedEventArgs> BeingObserved { get; set; } = new();

/// <summary>
/// Invoked before a player starts looking at SCP-173.
/// </summary>
public static Event<AddingObserverEventArgs> AddingObserver { get; set; } = new();

/// <summary>
/// Invoked after a player stops looking at SCP-173.
/// </summary>
public static Event<RemovedObserverEventArgs> RemovedObserver { get; set; } = new();

/// <summary>
/// Called before players near SCP-173 blink.
/// </summary>
Expand Down Expand Up @@ -71,5 +81,17 @@ public static class Scp173
/// </summary>
/// <param name="ev">The <see cref="BeingObservedEventArgs" /> instance.</param>
public static void OnBeingObserved(BeingObservedEventArgs ev) => BeingObserved.InvokeSafely(ev);

/// <summary>
/// Called before player starts looking at SCP-173.
/// </summary>
/// <param name="ev">The <see cref="AddingObserverEventArgs" /> instance.</param>
public static void OnAddingObserver(AddingObserverEventArgs ev) => AddingObserver.InvokeSafely(ev);

/// <summary>
/// Called after a player stops looking at SCP-173.
/// </summary>
/// <param name="ev">The <see cref="AddingObserverEventArgs" /> instance.</param>
public static void OnRemovedObserver(RemovedObserverEventArgs ev) => RemovedObserver.InvokeSafely(ev);
}
}
117 changes: 117 additions & 0 deletions EXILED/Exiled.Events/Patches/Events/Scp173/Observers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// -----------------------------------------------------------------------
// <copyright file="Observers.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.Scp173
{
using System.Collections.Generic;
using System.Reflection.Emit;

using API.Features;
using Attributes;
using Exiled.API.Features.Pools;
using Exiled.Events.EventArgs.Scp173;
using HarmonyLib;
using PlayerRoles.PlayableScps.Scp173;
using PlayerRoles.Subroutines;

using static HarmonyLib.AccessTools;

/// <summary>
/// Patches <see cref="Scp173ObserversTracker.UpdateObserver(ReferenceHub)" />.
/// Adds the <see cref="Handlers.Scp173.AddingObserver" /> event.
/// </summary>
[EventPatch(typeof(Handlers.Scp173), nameof(Handlers.Scp173.AddingObserver))]
[EventPatch(typeof(Handlers.Scp173), nameof(Handlers.Scp173.RemovedObserver))]
[HarmonyPatch(typeof(Scp173ObserversTracker), nameof(Scp173ObserversTracker.UpdateObserver))]
internal static class Observers
{
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.Pool.Get(instructions);

// AddingObserver patch
Label returnLabel = generator.DefineLabel();
LocalBuilder ev = generator.DeclareLocal(typeof(AddingObserverEventArgs));

int index = newInstructions.FindIndex(x => x.Calls(Method(typeof(HashSet<ReferenceHub>), nameof(HashSet<ReferenceHub>.Add)))) + 2;
newInstructions[index].labels.Add(returnLabel);

newInstructions.InsertRange(index, new CodeInstruction[]
{
// Player.Get(Owner);
new(OpCodes.Ldarg_0),
new(OpCodes.Callvirt, PropertyGetter(typeof(StandardSubroutine<Scp173Role>), nameof(StandardSubroutine<Scp173Role>.Owner))),
new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),

// Player.Get(ply);
new(OpCodes.Ldarg_1),
new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),

// true
new(OpCodes.Ldc_I4_1),

// AddingObserverEventArgs ev = new(Player.Get(Owner), Player.Get(ply), true);
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(AddingObserverEventArgs))[0]),
new(OpCodes.Stloc, ev),

// Scp173.OnAddingObserver(ev);
new(OpCodes.Ldloc, ev),
new(OpCodes.Call, Method(typeof(Handlers.Scp173), nameof(Handlers.Scp173.OnAddingObserver))),

// if (!ev.IsAllowed)
new(OpCodes.Ldloc, ev),
new(OpCodes.Callvirt,
PropertyGetter(typeof(AddingObserverEventArgs), nameof(AddingObserverEventArgs.IsAllowed))),
new(OpCodes.Brtrue_S, returnLabel),

// Observers.Remove(ply);
new(OpCodes.Ldarg_0),
new(OpCodes.Ldfld, Field(typeof(Scp173ObserversTracker), nameof(Scp173ObserversTracker.Observers))),
new(OpCodes.Ldarg_1),
new(OpCodes.Callvirt, Method(typeof(HashSet<ReferenceHub>), nameof(HashSet<ReferenceHub>.Remove))),
new(OpCodes.Pop),

// return 0;
new(OpCodes.Ldc_I4_0),
new(OpCodes.Ret),
});

// RemoveObserver patch
int index2 = newInstructions.FindLastIndex(x => x.Calls(Method(typeof(HashSet<ReferenceHub>), nameof(HashSet<ReferenceHub>.Remove)))) + 2;

LocalBuilder ev2 = generator.DeclareLocal(typeof(RemovedObserverEventArgs));

newInstructions.InsertRange(index2, new CodeInstruction[]
{
// Player.Get(Owner);
new(OpCodes.Ldarg_0),
new(OpCodes.Callvirt,
PropertyGetter(typeof(StandardSubroutine<Scp173Role>), nameof(StandardSubroutine<Scp173Role>.Owner))),
new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),

// Player.Get(ply);
new(OpCodes.Ldarg_1),
new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),

// new RemovedObserverEventArgs(Player.Get(Owner), Player.Get(ply));
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovedObserverEventArgs))[0]),
new(OpCodes.Stloc, ev2),

// Scp173.OnRemovingObserver(new RemovedObserverEventArgs(Player.Get(Owner), Player.Get(ply)));
new(OpCodes.Ldloc, ev2),
new(OpCodes.Call, Method(typeof(Handlers.Scp173), nameof(Handlers.Scp173.OnRemovedObserver))),
});

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

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