Skip to content

Commit c782ee2

Browse files
authored
feat: RemovingTarget event for 096 (#576)
* fix KeybindSetting and RoundEnd transpiler I think it all works, I ran server and no errors, I got on and ended round and no errors * Fix Scale (ScaleController is mid) Fix ChangingLeverStatus event I think these work * Add event (was painful cuz of previous shenanigans)
1 parent 4296fa9 commit c782ee2

3 files changed

Lines changed: 219 additions & 0 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="RemovingTargetEventArgs.cs" company="ExMod Team">
3+
// Copyright (c) ExMod Team. All rights reserved.
4+
// Licensed under the CC BY-SA 3.0 license.
5+
// </copyright>
6+
// -----------------------------------------------------------------------
7+
8+
namespace Exiled.Events.EventArgs.Scp096
9+
{
10+
using API.Features;
11+
12+
using Interfaces;
13+
14+
using Scp096Role = API.Features.Roles.Scp096Role;
15+
16+
/// <summary>
17+
/// Contains all information after removing a target from SCP-096.
18+
/// </summary>
19+
public class RemovingTargetEventArgs : IScp096Event, IDeniableEvent
20+
{
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="RemovingTargetEventArgs" /> class.
23+
/// </summary>
24+
/// <param name="scp096">
25+
/// <inheritdoc cref="Player" />
26+
/// </param>
27+
/// <param name="target">
28+
/// <inheritdoc cref="Target" />
29+
/// </param>
30+
/// <param name="isAllowed">
31+
/// <inheritdoc cref="IsAllowed" />
32+
/// </param>
33+
public RemovingTargetEventArgs(Player scp096, Player target, bool isAllowed = true)
34+
{
35+
Player = scp096;
36+
Scp096 = scp096.Role.As<Scp096Role>();
37+
Target = target;
38+
IsAllowed = isAllowed;
39+
}
40+
41+
/// <summary>
42+
/// Gets the <see cref="Player" /> that is controlling SCP-096.
43+
/// </summary>
44+
public Player Player { get; }
45+
46+
/// <inheritdoc/>
47+
public Scp096Role Scp096 { get; }
48+
49+
/// <summary>
50+
/// Gets the <see cref="Player" /> being removed as a target.
51+
/// </summary>
52+
public Player Target { get; }
53+
54+
/// <summary>
55+
/// Gets or sets a value indicating whether the target is allowed to be removed.
56+
/// </summary>
57+
public bool IsAllowed { get; set; }
58+
}
59+
}

EXILED/Exiled.Events/Handlers/Scp096.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public static class Scp096
3232
/// </summary>
3333
public static Event<AddingTargetEventArgs> AddingTarget { get; set; } = new();
3434

35+
/// <summary>
36+
/// Invoked before removing a target from SCP-096.
37+
/// </summary>
38+
public static Event<RemovingTargetEventArgs> RemovingTarget { get; set; } = new();
39+
3540
/// <summary>
3641
/// Invoked before SCP-096 begins prying open a gate.
3742
/// </summary>
@@ -65,6 +70,12 @@ public static class Scp096
6570
/// <param name="ev">The <see cref="AddingTargetEventArgs" /> instance.</param>
6671
public static void OnAddingTarget(AddingTargetEventArgs ev) => AddingTarget.InvokeSafely(ev);
6772

73+
/// <summary>
74+
/// Called before removing a target from SCP-096.
75+
/// </summary>
76+
/// <param name="ev">The <see cref="RemovingTargetEventArgs" /> instance.</param>
77+
public static void OnRemovingTarget(RemovingTargetEventArgs ev) => RemovingTarget.InvokeSafely(ev);
78+
6879
/// <summary>
6980
/// Called before SCP-096 begins prying open a gate.
7081
/// </summary>
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="RemovingTarget.cs" company="ExMod Team">
3+
// Copyright (c) ExMod Team. All rights reserved.
4+
// Licensed under the CC BY-SA 3.0 license.
5+
// </copyright>
6+
// -----------------------------------------------------------------------
7+
8+
namespace Exiled.Events.Patches.Events.Scp096
9+
{
10+
using System;
11+
using System.Collections.Generic;
12+
using System.Diagnostics;
13+
using System.Reflection.Emit;
14+
15+
using API.Features;
16+
using API.Features.Pools;
17+
using Exiled.Events.Attributes;
18+
using Exiled.Events.EventArgs.Scp096;
19+
using HarmonyLib;
20+
using PlayerRoles.PlayableScps.Scp096;
21+
using PlayerRoles.Subroutines;
22+
23+
using static HarmonyLib.AccessTools;
24+
25+
/// <summary>
26+
/// Patches <see cref="Scp096TargetsTracker.RemoveTarget(ReferenceHub)" /> and <see cref="Scp096TargetsTracker.ClearAllTargets()" />.
27+
/// Adds the <see cref="Scp096.RemovingTarget" /> event.
28+
/// </summary>
29+
[EventPatch(typeof(Handlers.Scp096), nameof(Handlers.Scp096.RemovingTarget))]
30+
[HarmonyPatch(typeof(Scp096TargetsTracker))]
31+
internal static class RemovingTarget
32+
{
33+
[HarmonyPatch(nameof(Scp096TargetsTracker.RemoveTarget))]
34+
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
35+
{
36+
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.Pool.Get(instructions);
37+
38+
Label retFalseLabel = generator.DefineLabel();
39+
Label runLabel = generator.DefineLabel();
40+
41+
// make game check contains instead of removing then forcibly running our event (so no spam event calls and is still deniable)
42+
newInstructions.Find(instruction => instruction.Calls(Method(typeof(HashSet<ReferenceHub>), nameof(HashSet<ReferenceHub>.Remove)))).operand = Method(typeof(HashSet<ReferenceHub>), nameof(HashSet<ReferenceHub>.Contains));
43+
44+
int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) - 1;
45+
46+
newInstructions[index].WithLabels(retFalseLabel);
47+
48+
index -= 1;
49+
50+
// integrate our condition into first if statement
51+
newInstructions.RemoveAt(index);
52+
53+
newInstructions.InsertRange(
54+
index,
55+
new CodeInstruction[]
56+
{
57+
// integrate our condition into first if statement
58+
new(OpCodes.Brfalse, retFalseLabel),
59+
60+
// Player.Get(base.Owner)
61+
new(OpCodes.Ldarg_0),
62+
new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine<Scp096Role>), nameof(StandardSubroutine<Scp096Role>.Owner))),
63+
new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
64+
65+
// Player.Get(target)
66+
new(OpCodes.Ldarg_1),
67+
new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
68+
69+
// true
70+
new(OpCodes.Ldc_I4_1),
71+
72+
// RemovingTargetEventArgs ev = new(scp096, target, isAllowed)
73+
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovingTargetEventArgs))[0]),
74+
new(OpCodes.Dup),
75+
76+
// Handlers.Scp096.OnRemovingTarget(ev)
77+
new(OpCodes.Call, Method(typeof(Handlers.Scp096), nameof(Handlers.Scp096.OnRemovingTarget))),
78+
79+
// if (!ev.IsAllowed)
80+
// return;
81+
new(OpCodes.Callvirt, PropertyGetter(typeof(RemovingTargetEventArgs), nameof(RemovingTargetEventArgs.IsAllowed))),
82+
new(OpCodes.Brtrue, runLabel),
83+
});
84+
85+
index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) + 1;
86+
87+
// if allowed, remove target
88+
newInstructions.InsertRange(index, new[]
89+
{
90+
new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]).WithLabels(runLabel),
91+
new(OpCodes.Ldfld, Field(typeof(Scp096TargetsTracker), nameof(Scp096TargetsTracker.Targets))),
92+
new(OpCodes.Ldarg_1),
93+
new(OpCodes.Callvirt, Method(typeof(HashSet<ReferenceHub>), nameof(HashSet<ReferenceHub>.Remove))),
94+
new(OpCodes.Pop),
95+
});
96+
97+
for (int z = 0; z < newInstructions.Count; z++)
98+
yield return newInstructions[z];
99+
100+
ListPool<CodeInstruction>.Pool.Return(newInstructions);
101+
}
102+
103+
[HarmonyPatch(nameof(Scp096TargetsTracker.ClearAllTargets))]
104+
[HarmonyTranspiler]
105+
private static IEnumerable<CodeInstruction> Transpiler2(IEnumerable<CodeInstruction> instructions)
106+
{
107+
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.Pool.Get(instructions);
108+
109+
object continueLabel = newInstructions.Find(instruction => instruction.opcode == OpCodes.Br_S).operand;
110+
111+
const int offset = 1;
112+
int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Stloc_1) + offset;
113+
114+
newInstructions.InsertRange(
115+
index,
116+
new[]
117+
{
118+
// Player.Get(base.Owner)
119+
new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
120+
new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine<Scp096Role>), nameof(StandardSubroutine<Scp096Role>.Owner))),
121+
new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
122+
123+
// Player.Get(target)
124+
new(OpCodes.Ldloc_1),
125+
new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
126+
127+
// true
128+
new(OpCodes.Ldc_I4_1),
129+
130+
// RemovingTargetEventArgs ev = new(scp096, target, isAllowed)
131+
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovingTargetEventArgs))[0]),
132+
new(OpCodes.Dup),
133+
134+
// Handlers.Scp096.OnRemovingTarget(ev)
135+
new(OpCodes.Call, Method(typeof(Handlers.Scp096), nameof(Handlers.Scp096.OnRemovingTarget))),
136+
137+
// if (!ev.IsAllowed)
138+
// continue;
139+
new(OpCodes.Callvirt, PropertyGetter(typeof(RemovingTargetEventArgs), nameof(RemovingTargetEventArgs.IsAllowed))),
140+
new(OpCodes.Brfalse, continueLabel),
141+
});
142+
143+
for (int z = 0; z < newInstructions.Count; z++)
144+
yield return newInstructions[z];
145+
146+
ListPool<CodeInstruction>.Pool.Return(newInstructions);
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)