diff --git a/EXILED/Exiled.API/Enums/Scp939VisibilityStates.cs b/EXILED/Exiled.API/Enums/Scp939VisibilityStates.cs
new file mode 100644
index 000000000..d9be0eadc
--- /dev/null
+++ b/EXILED/Exiled.API/Enums/Scp939VisibilityStates.cs
@@ -0,0 +1,47 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Enums
+{
+ using Features.Roles;
+
+ ///
+ /// Unique identifier for a .
+ ///
+ public enum Scp939VisibilityState
+ {
+ ///
+ /// SCP-939 doesnt see an other player, by default FPC role logic.
+ ///
+ None,
+
+ ///
+ /// SCP-939 doesnt see an player, by basic SCP-939 logic.
+ ///
+ NotSeen,
+
+ ///
+ /// SCP-939 sees an other player, who is teammate SCP.
+ ///
+ SeenAsScp,
+
+ ///
+ /// SCP-939 sees an other player due the Alpha Warhead detonation.
+ ///
+ SeenByDetonation,
+
+ ///
+ /// SCP-939 sees an other player, due the base-game vision range logic.
+ ///
+ SeenByRange,
+
+ ///
+ /// SCP-939 sees an other player for a while, after it's out of range.
+ ///
+ SeenByLastTime,
+ }
+}
diff --git a/EXILED/Exiled.Events/EventArgs/Scp939/CreatedAmnesticCloudEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp939/CreatedAmnesticCloudEventArgs.cs
new file mode 100644
index 000000000..83a9d659f
--- /dev/null
+++ b/EXILED/Exiled.Events/EventArgs/Scp939/CreatedAmnesticCloudEventArgs.cs
@@ -0,0 +1,55 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.EventArgs.Scp939
+{
+ using API.Features;
+
+ using Exiled.API.Features.Hazards;
+ using Exiled.API.Features.Roles;
+ using Interfaces;
+
+ using PlayerRoles.PlayableScps.Scp939;
+
+ using Scp939Role = API.Features.Roles.Scp939Role;
+
+ ///
+ /// Contains all information after SCP-939 fully created target .
+ ///
+ public class CreatedAmnesticCloudEventArgs : IScp939Event, IHazardEvent
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public CreatedAmnesticCloudEventArgs(ReferenceHub hub, Scp939AmnesticCloudInstance cloud)
+ {
+ Player = Player.Get(hub);
+ AmnesticCloud = Hazard.Get(cloud);
+ Scp939 = Player.Role.As();
+ }
+
+ ///
+ public Player Player { get; }
+
+ ///
+ /// Gets the instance.
+ ///
+ public AmnesticCloudHazard AmnesticCloud { get; }
+
+ ///
+ public Scp939Role Scp939 { get; }
+
+ ///
+ public Hazard Hazard => AmnesticCloud;
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/EventArgs/Scp939/PlacedAmnesticCloudEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp939/PlacedAmnesticCloudEventArgs.cs
index d49aee7fa..d2545aad3 100644
--- a/EXILED/Exiled.Events/EventArgs/Scp939/PlacedAmnesticCloudEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Scp939/PlacedAmnesticCloudEventArgs.cs
@@ -17,7 +17,7 @@ namespace Exiled.Events.EventArgs.Scp939
///
/// Contains all information after SCP-939 used its amnestic cloud ability.
///
- public class PlacedAmnesticCloudEventArgs : IScp939Event
+ public class PlacedAmnesticCloudEventArgs : IScp939Event, IHazardEvent
{
///
/// Initializes a new instance of the class.
@@ -26,12 +26,12 @@ public class PlacedAmnesticCloudEventArgs : IScp939Event
///
///
///
- ///
+ ///
///
public PlacedAmnesticCloudEventArgs(ReferenceHub hub, Scp939AmnesticCloudInstance cloud)
{
Player = Player.Get(hub);
- AmnesticCloud = new AmnesticCloudHazard(cloud);
+ AmnesticCloud = Hazard.Get(cloud);
Scp939 = Player.Role.As();
}
@@ -47,5 +47,8 @@ public PlacedAmnesticCloudEventArgs(ReferenceHub hub, Scp939AmnesticCloudInstanc
///
public Scp939Role Scp939 { get; }
+
+ ///
+ public Hazard Hazard => AmnesticCloud;
}
}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/EventArgs/Scp939/ValidatingVisibilityEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp939/ValidatingVisibilityEventArgs.cs
index a14873c9a..fa3a93f30 100644
--- a/EXILED/Exiled.Events/EventArgs/Scp939/ValidatingVisibilityEventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Scp939/ValidatingVisibilityEventArgs.cs
@@ -1,4 +1,4 @@
-// -----------------------------------------------------------------------
+// -----------------------------------------------------------------------
//
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
@@ -8,41 +8,45 @@
namespace Exiled.Events.EventArgs.Scp939
{
using API.Features;
+
+ using Exiled.API.Enums;
using Exiled.API.Features.Roles;
using Interfaces;
///
- /// Contains all information before SCP-939 sees the player.
+ /// Contains all information before SCP-939 sees the player.
///
public class ValidatingVisibilityEventArgs : IScp939Event, IDeniableEvent
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
+ ///
+ ///
+ ///
///
- ///
+ ///
///
///
- /// The target being shown to SCP-939.
+ /// The target being shown to SCP-939.
///
- ///
- /// Whether SCP-939 is allowed to view the player.
- ///
- public ValidatingVisibilityEventArgs(ReferenceHub player, ReferenceHub target, bool isAllowed)
+ public ValidatingVisibilityEventArgs(Scp939VisibilityState state, ReferenceHub player, ReferenceHub target)
{
Player = Player.Get(player);
Scp939 = Player.Role.As();
Target = Player.Get(target);
- IsAllowed = isAllowed;
+ TargetVisibilityState = state;
+ IsAllowed = TargetVisibilityState is not(Scp939VisibilityState.NotSeen or Scp939VisibilityState.None);
+ IsLateSeen = TargetVisibilityState is Scp939VisibilityState.SeenByRange;
}
///
- /// Gets the player who's being shown to SCP-939.
+ /// Gets the player who's being shown to SCP-939.
///
public Player Target { get; }
///
- /// Gets the player who's controlling SCP-939.
+ /// Gets the player who's controlling SCP-939.
///
public Player Player { get; }
@@ -50,8 +54,19 @@ public ValidatingVisibilityEventArgs(ReferenceHub player, ReferenceHub target, b
public Scp939Role Scp939 { get; }
///
- /// Gets or sets a value indicating whether visibility can be validated.
+ /// Gets the info about base-game vision information.
///
- public bool IsAllowed { get; set; }
+ public Scp939VisibilityState TargetVisibilityState { get; }
+
+ ///
+ /// Gets or sets a value indicating whether or not the SCP-939 will detect that vision as .
+ ///
+ ///
+ /// Works only when = , and makes player visible to SCP-939 for a while, after it's out of range.
+ ///
+ public bool IsLateSeen { get; set; }
+
+ ///
+ public bool IsAllowed { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/EXILED/Exiled.Events/Handlers/Scp939.cs b/EXILED/Exiled.Events/Handlers/Scp939.cs
index 04e4997dd..7ccda5f61 100644
--- a/EXILED/Exiled.Events/Handlers/Scp939.cs
+++ b/EXILED/Exiled.Events/Handlers/Scp939.cs
@@ -8,6 +8,7 @@
namespace Exiled.Events.Handlers
{
#pragma warning disable SA1623 // Property summary documentation should match accessors
+ using Exiled.API.Features.Hazards;
using Exiled.Events.EventArgs.Scp939;
using Exiled.Events.Features;
@@ -37,6 +38,11 @@ public static class Scp939
///
public static Event PlacedAmnesticCloud { get; set; } = new();
+ ///
+ /// Invoked after SCP-939 fully spawned .
+ ///
+ public static Event CreatedAmnesticCloud { get; set; } = new();
+
///
/// Invoked before SCP-939 plays a stolen voice.
///
@@ -92,6 +98,12 @@ public static class Scp939
/// The instance.
public static void OnPlacedAmnesticCloud(PlacedAmnesticCloudEventArgs ev) => PlacedAmnesticCloud.InvokeSafely(ev);
+ ///
+ /// Called after SCP-939 fully spawned .
+ ///
+ /// The instance.
+ public static void OnCreatedAmnesticCloud(CreatedAmnesticCloudEventArgs ev) => CreatedAmnesticCloud.InvokeSafely(ev);
+
///
/// Called before SCP-939 plays a stolen voice.
///
diff --git a/EXILED/Exiled.Events/Patches/Events/Scp939/CreatedAmnesticCloud.cs b/EXILED/Exiled.Events/Patches/Events/Scp939/CreatedAmnesticCloud.cs
new file mode 100644
index 000000000..fc11f5954
--- /dev/null
+++ b/EXILED/Exiled.Events/Patches/Events/Scp939/CreatedAmnesticCloud.cs
@@ -0,0 +1,71 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.Patches.Events.Scp939
+{
+ using System.Collections.Generic;
+ using System.Reflection.Emit;
+
+ using Exiled.API.Features.Pools;
+ using Exiled.Events.Attributes;
+ using Exiled.Events.EventArgs.Scp939;
+ using Exiled.Events.Handlers;
+
+ using HarmonyLib;
+
+ using PlayerRoles.PlayableScps.Scp939;
+
+ using static HarmonyLib.AccessTools;
+
+ ///
+ /// Patches setter.
+ /// to add the event.
+ ///
+ [EventPatch(typeof(Scp939), nameof(Scp939.CreatedAmnesticCloud))]
+ [HarmonyPatch(typeof(Scp939AmnesticCloudInstance), nameof(Scp939AmnesticCloudInstance.State), MethodType.Setter)]
+ internal static class CreatedAmnesticCloud
+ {
+ private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(instructions);
+
+ LocalBuilder hub = generator.DeclareLocal(typeof(ReferenceHub));
+
+ Label ret = generator.DefineLabel();
+
+ newInstructions.InsertRange(
+ newInstructions.Count - 1,
+ new[]
+ {
+ // if (!ReferenceHub.TryGetHubNetID(_syncOwner, out ReferenceHub owner))
+ // return;
+ new CodeInstruction(OpCodes.Ldarg_0),
+ new(OpCodes.Ldfld, Field(typeof(Scp939AmnesticCloudInstance), nameof(Scp939AmnesticCloudInstance._syncOwner))),
+ new(OpCodes.Ldloca_S, hub.LocalIndex),
+ new(OpCodes.Call, Method(typeof(ReferenceHub), nameof(ReferenceHub.TryGetHubNetID))),
+ new(OpCodes.Brfalse_S, ret),
+
+ // owner
+ new(OpCodes.Ldloc_S, hub.LocalIndex),
+
+ // this
+ new(OpCodes.Ldarg_0),
+
+ // Scp939.OnCreatedAmnesticCloud(new CreatedAmnesticCloudEventArgs(owner, this));
+ new(OpCodes.Newobj, GetDeclaredConstructors(typeof(CreatedAmnesticCloudEventArgs))[0]),
+ new(OpCodes.Call, Method(typeof(Scp939), nameof(Scp939.OnCreatedAmnesticCloud))),
+ });
+
+ newInstructions[newInstructions.Count - 1].labels.Add(ret);
+
+ for (int z = 0; z < newInstructions.Count; z++)
+ yield return newInstructions[z];
+
+ ListPool.Pool.Return(newInstructions);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Patches/Events/Scp939/ValidatingVisibility.cs b/EXILED/Exiled.Events/Patches/Events/Scp939/ValidatingVisibility.cs
index b8d3462f0..d12e690c6 100644
--- a/EXILED/Exiled.Events/Patches/Events/Scp939/ValidatingVisibility.cs
+++ b/EXILED/Exiled.Events/Patches/Events/Scp939/ValidatingVisibility.cs
@@ -1,4 +1,4 @@
-// -----------------------------------------------------------------------
+// -----------------------------------------------------------------------
//
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
@@ -7,26 +7,147 @@
namespace Exiled.Events.Patches.Events.Scp939
{
- #pragma warning disable SA1313
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection.Emit;
+
+ using Exiled.API.Enums;
+ using Exiled.API.Extensions;
+ using Exiled.API.Features;
+ using Exiled.API.Features.Pools;
using Exiled.Events.Attributes;
using Exiled.Events.EventArgs.Scp939;
using HarmonyLib;
+ using InventorySystem.Items;
+ using Mirror;
+
using PlayerRoles.PlayableScps.Scp939;
+ using static HarmonyLib.AccessTools;
+
///
/// Patches
- /// to add the event.
+ /// to add the event.
///
[EventPatch(typeof(Handlers.Scp939), nameof(Handlers.Scp939.ValidatingVisibility))]
[HarmonyPatch(typeof(Scp939VisibilityController), nameof(Scp939VisibilityController.ValidateVisibility))]
internal class ValidatingVisibility
{
- private static void Postfix(Scp939VisibilityController __instance, ReferenceHub hub, ref bool __result)
+ private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(instructions);
+
+ LocalBuilder ev = generator.DeclareLocal(typeof(ValidatingVisibilityEventArgs));
+
+ Label ret = generator.DefineLabel();
+ Label end = generator.DefineLabel();
+
+ int offset = 0;
+ int index = newInstructions.FindIndex(i => i.LoadsConstant(0)) + offset;
+
+ newInstructions[index].labels.Add(ret);
+
+ newInstructions.InsertRange(index, StaticCallEvent(generator, ev, ret, newInstructions[index], Scp939VisibilityState.None, false));
+
+ offset = 0;
+ index = newInstructions.FindIndex(i => i.LoadsConstant(1)) + offset;
+
+ newInstructions.InsertRange(index, StaticCallEvent(generator, ev, ret, newInstructions[index], Scp939VisibilityState.SeenAsScp));
+
+ offset = 2;
+ index = newInstructions.FindIndex(i => i.Calls(PropertyGetter(typeof(AlphaWarheadController), nameof(AlphaWarheadController.Detonated)))) + offset;
+
+ newInstructions.InsertRange(index, StaticCallEvent(generator, ev, ret, newInstructions[index], Scp939VisibilityState.SeenByDetonation));
+
+ offset = 0;
+ index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldloc_3) + offset;
+
+ // just pre-check for SeenByLastTime or NotSeen VisibilityState, and then il inject
+ newInstructions.InsertRange(index, Enumerable.Concat(
+ new CodeInstruction[]
+ {
+ new CodeInstruction(OpCodes.Ldc_I4, (int)Scp939VisibilityState.NotSeen).MoveLabelsFrom(newInstructions[index]),
+ new(OpCodes.Ldloc_3),
+ new(OpCodes.Brfalse_S, end),
+ new(OpCodes.Pop),
+ new(OpCodes.Ldc_I4, (int)Scp939VisibilityState.SeenByLastTime),
+ new CodeInstruction(OpCodes.Nop).WithLabels(end),
+ },
+ CallEvent(generator, ev, ret)));
+
+ offset = 0;
+ index = newInstructions.FindLastIndex(i => i.LoadsField(Field(typeof(Scp939VisibilityController), nameof(Scp939VisibilityController.LastSeen)))) + offset;
+
+ newInstructions.InsertRange(index, StaticCallEvent(generator, ev, ret, newInstructions[index], Scp939VisibilityState.SeenByRange));
+
+ for (int z = 0; z < newInstructions.Count; z++)
+ yield return newInstructions[z];
+
+ ListPool.Pool.Return(newInstructions);
+ }
+
+ // helper method for injecting instructions
+ private static IEnumerable StaticCallEvent(ILGenerator generator, LocalBuilder ev, Label ret, CodeInstruction insertInstuction, Scp939VisibilityState state, bool setLabel = true)
+ {
+ CodeInstruction first = new CodeInstruction(OpCodes.Ldc_I4, (int)state);
+
+ if (setLabel)
+ {
+ first.labels.AddRange(insertInstuction.ExtractLabels());
+ }
+
+ yield return first;
+
+ foreach (CodeInstruction z in CallEvent(generator, ev, ret))
+ {
+ yield return z;
+ }
+ }
+
+ // mail il logic
+ private static IEnumerable CallEvent(ILGenerator generator, LocalBuilder ev, Label ret)
+ {
+ Label cnt = generator.DefineLabel();
+
+ // ...VisibilityState loaded in stack
+ // ValidatingVisibilityEventArgs ev = new(state, scp939, target)
+ yield return new(OpCodes.Ldarg_0);
+ yield return new(OpCodes.Callvirt, PropertyGetter(typeof(Scp939VisibilityController), nameof(Scp939VisibilityController.Owner)));
+ yield return new(OpCodes.Ldarg_1);
+ yield return new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ValidatingVisibilityEventArgs))[0]);
+ yield return new(OpCodes.Dup);
+ yield return new(OpCodes.Dup);
+ yield return new(OpCodes.Stloc_S, ev.LocalIndex);
+
+ // Scp939.OnValidatingVisibility(ev)
+ // if (!ev.IsAllowed)
+ // return false;
+ yield return new(OpCodes.Call, Method(typeof(Handlers.Scp939), nameof(Handlers.Scp939.OnValidatingVisibility)));
+ yield return new(OpCodes.Callvirt, PropertyGetter(typeof(ValidatingVisibilityEventArgs), nameof(ValidatingVisibilityEventArgs.IsAllowed)));
+ yield return new(OpCodes.Brfalse_S, ret);
+
+ // if (IsLateSeen)
+ // ValidatingVisibility.SetToLastSeen(target);
+ // return true;
+ yield return new(OpCodes.Ldloc_S, ev.LocalIndex);
+ yield return new(OpCodes.Callvirt, PropertyGetter(typeof(ValidatingVisibilityEventArgs), nameof(ValidatingVisibilityEventArgs.IsLateSeen)));
+ yield return new(OpCodes.Brfalse_S, cnt);
+
+ yield return new(OpCodes.Ldarg_1);
+ yield return new(OpCodes.Call, Method(typeof(ValidatingVisibility), nameof(ValidatingVisibility.SetToLastSeen)));
+
+ yield return new CodeInstruction(OpCodes.Ldc_I4_1).WithLabels(cnt);
+ yield return new(OpCodes.Ret);
+ }
+
+ private static void SetToLastSeen(ReferenceHub target)
{
- ValidatingVisibilityEventArgs ev = new(__instance.Owner, hub, __result);
- Handlers.Scp939.OnValidatingVisibility(ev);
- __result = ev.IsAllowed;
+ Scp939VisibilityController.LastSeen[target.netId] = new Scp939VisibilityController.LastSeenInfo()
+ {
+ Time = NetworkTime.time,
+ };
}
}
-}
\ No newline at end of file
+}