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 +}