Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions EXILED/Exiled.Events/EventArgs/Scp173/BeingObservedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// -----------------------------------------------------------------------
// <copyright file="BeingObservedEventArgs.cs" company="Exiled Team">
// Copyright (c) Exiled 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 Exiled.Events.EventArgs.Interfaces;

/// <summary>
/// Contains all the information before SCP-173 is observed.
/// </summary>
public class BeingObservedEventArgs : IScp173Event, IDeniableEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="BeingObservedEventArgs" /> class.
/// </summary>
/// <param name="target">
/// The <see cref="Exiled.API.Features.Player"/> instance of the target.
/// </param>
/// <param name="scp173">
/// The <see cref="Exiled.API.Features.Player"/> instance of the SCP-173.
/// </param>
/// <param name="isAllowed">
/// Whether or not the target will be counted as observing the SCP-173.
/// </param>
public BeingObservedEventArgs(API.Features.Player target, API.Features.Player scp173, bool isAllowed = true)
{
Target = target;
Player = scp173;
Scp173 = scp173.Role.As<Scp173Role>();
IsAllowed = isAllowed;
}

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

/// <summary>
/// Gets the player who's being observed.
/// </summary>
public Player Player { get; }

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

/// <summary>
/// Gets or sets a value indicating whether or not the player can be counted as observing.
/// </summary>
public bool IsAllowed { get; set; }
}
}
13 changes: 12 additions & 1 deletion EXILED/Exiled.Events/Handlers/Scp173.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ public static class Scp173
/// </summary>
public static Event<UsingBreakneckSpeedsEventArgs> UsingBreakneckSpeeds { get; set; } = new();

/// <summary>
/// Invoked before SCP-173 is observed.
/// </summary>
public static Event<BeingObservedEventArgs> BeingObserved { get; set; } = new();

/// <summary>
/// Called before players near SCP-173 blink.
/// </summary>
Expand All @@ -60,5 +65,11 @@ public static class Scp173
/// </summary>
/// <param name="ev">The <see cref="UsingBreakneckSpeedsEventArgs" /> instance.</param>
public static void OnUsingBreakneckSpeeds(UsingBreakneckSpeedsEventArgs ev) => UsingBreakneckSpeeds.InvokeSafely(ev);

/// <summary>
/// Called before Scp 173 is observed.
/// </summary>
/// <param name="ev">The <see cref="BeingObservedEventArgs" /> instance.</param>
public static void OnBeingObserved(BeingObservedEventArgs ev) => BeingObserved.InvokeSafely(ev);
}
}
}
89 changes: 89 additions & 0 deletions EXILED/Exiled.Events/Patches/Events/Scp173/BeingObserved.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// -----------------------------------------------------------------------
// <copyright file="BeingObserved.cs" company="Exiled Team">
// Copyright (c) Exiled Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.Events.Patches.Events.Scp173
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

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

using static HarmonyLib.AccessTools;

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

Label continueLabel = generator.DefineLabel();

const int offset = -4;
var scp173ExecuteEvent = typeof(EventManager)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.FirstOrDefault(m => m.Name == nameof(EventManager.ExecuteEvent)
&& !m.IsGenericMethod);
int index = newInstructions.FindLastIndex(i => i.Calls(scp173ExecuteEvent)) + offset;

newInstructions.InsertRange(
index,
new CodeInstruction[]
{
// Player.Get(target)
new(OpCodes.Ldarg_1),
new(OpCodes.Call, Method(typeof(API.Features.Player), nameof(API.Features.Player.Get), new[] { typeof(ReferenceHub) })),

// Player.Get(base.Owner)
new(OpCodes.Ldarg_0),
new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine<Scp173Role>), nameof(StandardSubroutine<Scp173Role>.Owner))),
new(OpCodes.Call, Method(typeof(API.Features.Player), nameof(API.Features.Player.Get), new[] { typeof(ReferenceHub) })),

// true
new(OpCodes.Ldc_I4_1),

// BeingObservedEventArgs ev = new(Player, Player, bool)
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(BeingObservedEventArgs))[0]),
new(OpCodes.Dup),

// Handlers.Scp173.OnBeingObserved(ev)
new(OpCodes.Call, Method(typeof(Handlers.Scp173), nameof(Handlers.Scp173.OnBeingObserved))),

// if (ev.IsAllowed)
// goto continueLabel
new(OpCodes.Callvirt, PropertyGetter(typeof(BeingObservedEventArgs), nameof(BeingObservedEventArgs.IsAllowed))),
new(OpCodes.Brtrue, continueLabel),

// return false
new(OpCodes.Ldc_I4_0),
new(OpCodes.Ret),

// continueLabel:
new CodeInstruction(OpCodes.Nop).WithLabels(continueLabel),
});

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

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