Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More features and events for SCP-1507 #2348

Merged
merged 14 commits into from
Dec 23, 2023
6 changes: 5 additions & 1 deletion Exiled.API/Features/Ragdoll.cs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,11 @@ public static Ragdoll CreateAndSpawn(RoleTypeId roleType, string name, string de
/// <param name="ragdoll">The <see cref="BasicRagdoll"/> to get.</param>
/// <returns>A <see cref="Ragdoll"/> or <see langword="null"/> if not found.</returns>
public static Ragdoll Get(BasicRagdoll ragdoll) => ragdoll == null ? null :
BasicRagdollToRagdoll.TryGetValue(ragdoll, out Ragdoll doll) ? doll : new Ragdoll(ragdoll);
BasicRagdollToRagdoll.TryGetValue(ragdoll, out Ragdoll doll) ? doll : ragdoll switch
{
PlayerRoles.PlayableScps.Scp1507.Scp1507Ragdoll scp1507Ragdoll => new Scp1507Ragdoll(scp1507Ragdoll),
_ => new Ragdoll(ragdoll)
};

/// <summary>
/// Gets the <see cref="IEnumerable{T}"/> of <see cref="Ragdoll"/> belonging to the <see cref="Player"/>, if any.
Expand Down
62 changes: 59 additions & 3 deletions Exiled.API/Features/Roles/Scp1507Role.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

namespace Exiled.API.Features.Roles
{
using System.Collections.Generic;
using System.Linq;

using Exiled.API.Enums;
using Exiled.API.Interfaces;
using PlayerRoles;
Expand Down Expand Up @@ -58,6 +61,9 @@ public Scp1507Role(BaseRole baseRole)
/// <inheritdoc/>
public SubroutineManagerModule SubroutineModule { get; }

/// <inheritdoc/>
public HumeShieldModuleBase HumeShieldModule { get; }

/// <summary>
/// Gets the <see cref="Scp1507AttackAbility"/> for this role.
/// </summary>
Expand Down Expand Up @@ -87,7 +93,57 @@ public float Damage
/// </summary>
public float AttackDelay => AttackAbility.AttackDelay;

/// <inheritdoc/>
public HumeShieldModuleBase HumeShieldModule { get; }
/// <summary>
/// Gets or sets a list with flamingos, which are close to owner.
/// </summary>
public IEnumerable<Player> NearbyFlamingos
{
get => SwarmAbility._nearbyFlamingos.Select(x => Player.Get(x._lastOwner));
set
{
SwarmAbility._nearbyFlamingos.Clear();

foreach (var player in value.Where(x => x.Role.Is<Scp1507Role>(out _)))
SwarmAbility._nearbyFlamingos.Add(player.Role.As<Scp1507Role>().Base);
}
}

/// <summary>
/// Gets or sets a list with all flamingos.
/// </summary>
public IEnumerable<Player> AllFlamingos
{
get => SwarmAbility._entireFlock.Select(x => Player.Get(x._lastOwner));
set
{
SwarmAbility._entireFlock.Clear();

foreach (var player in value.Where(x => x.Role.Is<Scp1507Role>(out _)))
SwarmAbility._entireFlock.Add(player.Role.As<Scp1507Role>().Base);

SwarmAbility._flockSize = (byte)(SwarmAbility._entireFlock.Count - 1);
}
}

/// <summary>
/// Gets or sets a multiplier for healing.
/// </summary>
public float Multiplier
{
get => SwarmAbility.Multiplier;
set => SwarmAbility.Multiplier = value;
}

/// <summary>
/// Tries to attack door.
/// </summary>
/// <returns><see langword="true"/> if successfully. Otherwise, <see langword="false"/>.</returns>
/// <remarks>This method does not modify game logic, so if you want this method to work correctly, make sure that player is staying in front of the door.</remarks>
public bool TryAttackDoor() => AttackAbility.TryAttackDoor();

/// <summary>
/// Forces a SCP-1507 to scream.
/// </summary>
public void Scream() => VocalizeAbility.ServerScream();
}
}
}
74 changes: 74 additions & 0 deletions Exiled.API/Features/Scp1507Ragdoll.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// -----------------------------------------------------------------------
// <copyright file="Scp1507Ragdoll.cs" company="Exiled Team">
// Copyright (c) Exiled Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.API.Features
{
using Exiled.API.Extensions;
using Exiled.API.Features.Roles;
using Exiled.API.Interfaces;
using PlayerRoles;

using Scp1507BaseRagdoll = PlayerRoles.PlayableScps.Scp1507.Scp1507Ragdoll;

/// <summary>
/// A wrapper for <see cref="Scp1507BaseRagdoll"/>.
/// </summary>
public class Scp1507Ragdoll : Ragdoll, IWrapper<Scp1507BaseRagdoll>
{
/// <summary>
/// Initializes a new instance of the <see cref="Scp1507Ragdoll"/> class.
/// </summary>
/// <param name="ragdoll">The encapsulated <see cref="Scp1507BaseRagdoll"/>.</param>
internal Scp1507Ragdoll(Scp1507BaseRagdoll ragdoll)
: base(ragdoll)
{
Base = ragdoll;
}

/// <inheritdoc/>
public new Scp1507BaseRagdoll Base { get; }

/// <summary>
/// Gets or sets current progress of revival process.
/// </summary>
public float RevivalProgress
{
get => Base._revivalProgress;
set => Base.Network_revivalProgress = value;
}

/// <summary>
/// Gets or sets a value indicating whether or not this ragdoll has been revived.
/// </summary>
public bool IsRevived
{
get => Base._hasAlreadyRevived;
set => Base.Network_hasAlreadyRevived = value;
}

/// <summary>
/// Gets or sets amount of time when ragdoll was reset last time.
/// </summary>
public double ResetTime
{
get => Base._lastResetTime;
set => Base.Network_lastResetTime = value;
}

/// <summary>
/// Spawns a variant from available ragdolls for chosen role.
/// </summary>
/// <param name="role">Role. Can be <see cref="RoleTypeId.Flamingo"/>, <see cref="RoleTypeId.AlphaFlamingo"/> or <see cref="RoleTypeId.ZombieFlamingo"/>.</param>
public void SpawnVariant(RoleTypeId role) => Base.SpawnVariant(role);

/// <summary>
/// Vocalizes ragdoll.
/// </summary>
/// <param name="player">Player who vocalizes. If <see langword="null"/>, will be chosen random.</param>
public void Vocalize(Player player = null) => Base.OnVocalize((player ?? Player.Get(x => x.Role.Is(out Scp1507Role _)).GetRandomValue()).ReferenceHub);
}
}
6 changes: 6 additions & 0 deletions Exiled.Events/EventArgs/Scp049/FinishingRecallEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public FinishingRecallEventArgs(Player target, Player scp049, BasicRagdoll ragdo
Target = target;
Ragdoll = Ragdoll.Get(ragdoll);
IsAllowed = isAllowed;
IsFlamingo = ragdoll is PlayerRoles.PlayableScps.Scp1507.Scp1507Ragdoll;
}

/// <inheritdoc/>
Expand All @@ -63,5 +64,10 @@ public FinishingRecallEventArgs(Player target, Player scp049, BasicRagdoll ragdo
/// Gets or sets a value indicating whether or not the player can be revived.
/// </summary>
public bool IsAllowed { get; set; }

/// <summary>
/// Gets or sets a value indicating whether or not revived player should become a zombie flamingo.
/// </summary>
public bool IsFlamingo { get; set; }
}
}
40 changes: 40 additions & 0 deletions Exiled.Events/EventArgs/Scp1507/ScreamingEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// -----------------------------------------------------------------------
// <copyright file="ScreamingEventArgs.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.Scp1507
{
using Exiled.API.Features;
using Exiled.API.Features.Roles;
using Exiled.Events.EventArgs.Interfaces;

/// <summary>
/// Contains all information before SCP-1507 screams.
/// </summary>
public class ScreamingEventArgs : IScp1507Event, IDeniableEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="ScreamingEventArgs"/> class.
/// </summary>
/// <param name="player"><inheritdoc cref="Player"/></param>
/// <param name="isAllowed"><inheritdoc cref="IsAllowed"/></param>
public ScreamingEventArgs(Player player, bool isAllowed = true)
{
Player = player;
Scp1507 = player.Role.As<Scp1507Role>();
IsAllowed = isAllowed;
}

/// <inheritdoc/>
public Player Player { get; }

/// <inheritdoc/>
public Scp1507Role Scp1507 { get; }

/// <inheritdoc/>
public bool IsAllowed { get; set; }
}
}
11 changes: 11 additions & 0 deletions Exiled.Events/Handlers/Scp1507.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,21 @@ public static class Scp1507
/// </summary>
public static Event<AttackingDoorEventArgs> AttackingDoor { get; set; } = new();

/// <summary>
/// Invoked before SCP-1507 screams.
/// </summary>
public static Event<ScreamingEventArgs> Screaming { get; set; } = new();

/// <summary>
/// Called before SCP-1507 attacks door.
/// </summary>
/// <param name="ev">The <see cref="AttackingDoorEventArgs"/> instance.</param>
public static void OnAttackingDoor(AttackingDoorEventArgs ev) => AttackingDoor.InvokeSafely(ev);

/// <summary>
/// Called before SCP-1507 screams.
/// </summary>
/// <param name="ev">The <see cref="ScreamingEventArgs"/> instance.</param>
public static void OnScreaming(ScreamingEventArgs ev) => Screaming.InvokeSafely(ev);
}
}
20 changes: 19 additions & 1 deletion Exiled.Events/Patches/Events/Scp049/FinishingRecall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructi
{
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.Pool.Get(instructions);

const int offset = -5;
int offset = -5;
int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Newobj) + offset;

Label returnLabel = generator.DefineLabel();

LocalBuilder ev = generator.DeclareLocal(typeof(FinishingRecallEventArgs));

newInstructions.InsertRange(
index,
new[]
Expand All @@ -62,6 +64,8 @@ private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructi
// FinishingRecallEventArgs ev = new(target, scp049, BasicRagdoll, bool)
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(FinishingRecallEventArgs))[0]),
new(OpCodes.Dup),
new(OpCodes.Dup),
new(OpCodes.Stloc_S, ev.LocalIndex),

// Handlers.Scp049.OnFinishingRecall(ev)
new(OpCodes.Call, Method(typeof(Handlers.Scp049), nameof(Handlers.Scp049.OnFinishingRecall))),
Expand All @@ -72,6 +76,20 @@ private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructi
new(OpCodes.Brfalse_S, returnLabel),
});

offset = -2;
index = newInstructions.FindIndex(x => x.opcode == OpCodes.Isinst) + offset;

newInstructions.RemoveRange(index, 3);

newInstructions.InsertRange(
index,
new CodeInstruction[]
{
// ev.IsFlamingo
new(OpCodes.Ldloc_S, ev.LocalIndex),
new(OpCodes.Callvirt, PropertyGetter(typeof(FinishingRecallEventArgs), nameof(FinishingRecallEventArgs.IsFlamingo))),
});

newInstructions[newInstructions.Count - 1].WithLabels(returnLabel);

for (int z = 0; z < newInstructions.Count; z++)
Expand Down
71 changes: 71 additions & 0 deletions Exiled.Events/Patches/Events/Scp1507/Scream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// -----------------------------------------------------------------------
// <copyright file="Scream.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.Scp1507
{
using System.Collections.Generic;
using System.Reflection.Emit;

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

using static HarmonyLib.AccessTools;

/// <summary>
/// Patches <see cref="Scp1507VocalizeAbility.ServerProcessCmd"/>
/// to add <see cref="Handlers.Scp1507.Screaming"/> event.
/// </summary>
[EventPatch(typeof(Handlers.Scp1507), nameof(Handlers.Scp1507.Screaming))]
[HarmonyPatch(typeof(Scp1507VocalizeAbility), nameof(Scp1507VocalizeAbility.ServerProcessCmd))]
public class Scream
{
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.Pool.Get(instructions);

int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Ldarg_0);

Label retLabel = generator.DefineLabel();

newInstructions[newInstructions.Count - 1].labels.Add(retLabel);

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

// true
new(OpCodes.Ldc_I4_1),

// ScreamingEventArgs ev = new(Player, true);
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ScreamingEventArgs))[0]),
new(OpCodes.Dup),

// Handlers.Scp1507.OnScreaming(ev);
new(OpCodes.Call, Method(typeof(Handlers.Scp1507), nameof(Handlers.Scp1507.OnScreaming))),

// if (!ev.IsAllowed)
// goto retLabel;
new(OpCodes.Callvirt, PropertyGetter(typeof(ScreamingEventArgs), nameof(ScreamingEventArgs.IsAllowed))),
new(OpCodes.Brfalse_S, retLabel),
});

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

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