diff --git a/EXILED/EXILED.sln b/EXILED/EXILED.sln
index 60b7f5f72c..6c48410428 100644
--- a/EXILED/EXILED.sln
+++ b/EXILED/EXILED.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.4.33205.214
+# Visual Studio Version 18
+VisualStudioVersion = 18.3.11512.155 d18.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exiled.Events", "Exiled.Events\Exiled.Events.csproj", "{1E6C4350-5067-4CE7-91DB-6420D027A4FC}"
EndProject
@@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exiled.CustomRoles", "Exile
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{BE130A80-6819-44C6-AA1B-BF068DEA67FF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exiled.Utility", "Exiled.Utility\Exiled.Utility.csproj", "{70A4E012-0DBB-18AA-A972-770281EEF726}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -82,6 +84,12 @@ Global
{BE130A80-6819-44C6-AA1B-BF068DEA67FF}.Installer|Any CPU.Build.0 = Installer|Any CPU
{BE130A80-6819-44C6-AA1B-BF068DEA67FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE130A80-6819-44C6-AA1B-BF068DEA67FF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {70A4E012-0DBB-18AA-A972-770281EEF726}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {70A4E012-0DBB-18AA-A972-770281EEF726}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {70A4E012-0DBB-18AA-A972-770281EEF726}.Installer|Any CPU.ActiveCfg = Installer|Any CPU
+ {70A4E012-0DBB-18AA-A972-770281EEF726}.Installer|Any CPU.Build.0 = Installer|Any CPU
+ {70A4E012-0DBB-18AA-A972-770281EEF726}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {70A4E012-0DBB-18AA-A972-770281EEF726}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/EXILED/Exiled.API/Enums/PrefabType.cs b/EXILED/Exiled.API/Enums/PrefabType.cs
index 43936f1d80..c3ce00898d 100644
--- a/EXILED/Exiled.API/Enums/PrefabType.cs
+++ b/EXILED/Exiled.API/Enums/PrefabType.cs
@@ -12,6 +12,7 @@ namespace Exiled.API.Enums
///
/// Type of prefab.
///
+ ///
public enum PrefabType
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs
index 522a0a2113..9d500ca681 100644
--- a/EXILED/Exiled.API/Features/Player.cs
+++ b/EXILED/Exiled.API/Features/Player.cs
@@ -1199,6 +1199,11 @@ public bool IsSpectatable
///
public Footprint Footprint => new(ReferenceHub);
+ ///
+ /// Gets the player's UniqueLifeIdentifier.
+ ///
+ public int LifeIdentifier => ReferenceHub.roleManager.CurrentRole.UniqueLifeIdentifier;
+
///
/// Gets or sets a value indicating whether the player is spawn protected.
///
diff --git a/EXILED/Exiled.API/Features/PrefabHelper.cs b/EXILED/Exiled.API/Features/PrefabHelper.cs
index 179991e635..22579651ac 100644
--- a/EXILED/Exiled.API/Features/PrefabHelper.cs
+++ b/EXILED/Exiled.API/Features/PrefabHelper.cs
@@ -14,8 +14,8 @@ namespace Exiled.API.Features
using Exiled.API.Enums;
using Exiled.API.Features.Attributes;
-
using MapGeneration.Distributors;
+ using MapGeneration.RoomConnectors;
using Mirror;
using UnityEngine;
@@ -57,6 +57,11 @@ public static PrefabAttribute GetPrefabAttribute(this PrefabType prefabType)
/// Returns the .
public static GameObject GetPrefab(PrefabType prefabType)
{
+ if (prefabType is PrefabType.HCZOneSided or PrefabType.HCZTwoSided)
+ {
+ prefabType = PrefabType.HCZBreakableDoor;
+ }
+
if (Prefabs.TryGetValue(prefabType, out (GameObject, Component) prefab))
return prefab.Item1;
@@ -112,6 +117,17 @@ public static GameObject Spawn(PrefabType prefabType, Vector3 position = default
positionSync.Network_rotationY = (sbyte)Mathf.RoundToInt(rotation.Value.eulerAngles.y / 5.625F);
}
+ if (prefabType is PrefabType.HCZOneSided or PrefabType.HCZTwoSided or PrefabType.HCZBreakableDoor)
+ {
+ newGameObject.GetComponent().Network_syncBitmask = prefabType switch
+ {
+ PrefabType.HCZTwoSided => 0b00000000,
+ PrefabType.HCZOneSided => 0b00000001,
+ PrefabType.HCZBreakableDoor => 0b00000011,
+ _ => 0
+ };
+ }
+
NetworkServer.Spawn(newGameObject);
return newGameObject;
diff --git a/EXILED/Exiled.API/Features/Roles/Scp0492Role.cs b/EXILED/Exiled.API/Features/Roles/Scp0492Role.cs
index 7250aa4662..59aacd81ca 100644
--- a/EXILED/Exiled.API/Features/Roles/Scp0492Role.cs
+++ b/EXILED/Exiled.API/Features/Roles/Scp0492Role.cs
@@ -7,6 +7,8 @@
namespace Exiled.API.Features.Roles
{
+ using System.Collections.Generic;
+
using PlayerRoles;
using PlayerRoles.PlayableScps.HumeShield;
using PlayerRoles.PlayableScps.Scp049;
@@ -45,6 +47,11 @@ internal Scp0492Role(ZombieRole baseRole)
ConsumeAbility = zombieConsumeAbility492;
}
+ ///
+ /// Gets a list of players who are turned away from SCP-049 Sense Ability.
+ ///
+ public static HashSet TurnedPlayers { get; } = new(20);
+
///
public override RoleTypeId Type { get; } = RoleTypeId.Scp0492;
diff --git a/EXILED/Exiled.Events/Config.cs b/EXILED/Exiled.Events/Config.cs
index fd244f699b..4fea2e64ab 100644
--- a/EXILED/Exiled.Events/Config.cs
+++ b/EXILED/Exiled.Events/Config.cs
@@ -26,67 +26,18 @@ public sealed class Config : IConfig
[Description("Indicates whether events are patched only if they have delegates subscribed to them")]
public bool UseDynamicPatching { get; set; } = true;
- ///
- /// Gets or sets a value indicating whether SCP-173 can be blocked by the tutorial.
- ///
- [Description("Indicates whether SCP-173 can be blocked by the tutorial")]
- public bool CanTutorialBlockScp173 { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether SCP-096 can be triggered by the tutorial.
- ///
- [Description("Indicates whether SCP-096 can be triggered by the tutorial")]
- public bool CanTutorialTriggerScp096 { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether SCP-049 can activate the sense ability on tutorials.
- ///
- [Description("Indicates whether SCP-049 can sense tutorial players")]
- public bool CanScp049SenseTutorial { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether tutorial is affected by SCP-079 scan.
- ///
- [Description("Indicates whether tutorial is affected by SCP-079 scan.")]
- public bool TutorialNotAffectedByScp079Scan { get; set; } = false;
-
- ///
- /// Gets or sets a value indicating whether flashbangs flash original thrower.
- ///
- [Description("Indicates whether flashbangs flash original thrower.")]
- public bool CanFlashbangsAffectThrower { get; set; } = false;
-
///
/// Gets or sets a value indicating whether the name tracking (invisible EXILED version string added to the end of the server name) is enabled or not.
///
[Description("Indicates whether the name tracking (invisible EXILED version string added to the end of the server name) is enabled or not")]
public bool IsNameTrackingEnabled { get; set; } = true;
- ///
- /// Gets or sets a value indicating whether the inventory should be dropped before being set as spectator, through commands or plugins.
- ///
- [Description("Indicates whether the inventory should be dropped before being set as spectator, through commands or plugins")]
- public bool ShouldDropInventory { get; set; } = true;
-
- ///
- /// Gets or sets a value indicating whether the blood can be spawned.
- ///
- [Description("Indicates whether the blood can be spawned")]
- public bool CanSpawnBlood { get; set; } = true;
-
///
/// Gets or sets a value indicating whether keycard throw can affect basic doors.
///
- /// TODO: Make a poll about removing this config. (unimplemented since 9.6.0-beta7)
[Description("Indicates whether thrown keycards can affect doors that don't require any permissions")]
public bool CanKeycardThrowAffectDoors { get; set; } = false;
- ///
- /// Gets or sets a value indicating whether the SCP079 will recontained if there are no SCPs left.
- ///
- [Description("Indicates whether the SCP079 will recontained if there are no SCPs left.")]
- public bool RecontainScp079IfNoScpsLeft { get; set; } = true;
-
///
/// Gets or sets a value indicating whether configs has to be reloaded every time a round restarts.
///
diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs
index f01919aff5..fb3cbdaa20 100644
--- a/EXILED/Exiled.Events/Events.cs
+++ b/EXILED/Exiled.Events/Events.cs
@@ -65,7 +65,6 @@ public override void OnEnabled()
Handlers.Server.WaitingForPlayers += Handlers.Internal.Round.OnWaitingForPlayers;
Handlers.Server.RestartingRound += Handlers.Internal.Round.OnRestartingRound;
Handlers.Server.RoundStarted += Handlers.Internal.Round.OnRoundStarted;
- Handlers.Player.ChangingRole += Handlers.Internal.Round.OnChangingRole;
Handlers.Player.SpawningRagdoll += Handlers.Internal.Round.OnSpawningRagdoll;
Handlers.Scp049.ActivatingSense += Handlers.Internal.Round.OnActivatingSense;
Handlers.Player.Verified += Handlers.Internal.Round.OnVerified;
@@ -113,7 +112,6 @@ public override void OnDisabled()
Handlers.Server.WaitingForPlayers -= Handlers.Internal.Round.OnWaitingForPlayers;
Handlers.Server.RestartingRound -= Handlers.Internal.Round.OnRestartingRound;
Handlers.Server.RoundStarted -= Handlers.Internal.Round.OnRoundStarted;
- Handlers.Player.ChangingRole -= Handlers.Internal.Round.OnChangingRole;
Handlers.Player.SpawningRagdoll -= Handlers.Internal.Round.OnSpawningRagdoll;
Handlers.Scp049.ActivatingSense -= Handlers.Internal.Round.OnActivatingSense;
Handlers.Player.Verified -= Handlers.Internal.Round.OnVerified;
diff --git a/EXILED/Exiled.Events/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs
index cebcffa749..015a9fe7d8 100644
--- a/EXILED/Exiled.Events/Handlers/Internal/Round.cs
+++ b/EXILED/Exiled.Events/Handlers/Internal/Round.cs
@@ -82,13 +82,6 @@ public static void OnRestartingRound()
///
public static void OnRoundStarted() => MultiAdminFeatures.CallEvent(MultiAdminFeatures.EventType.ROUND_START);
- ///
- public static void OnChangingRole(ChangingRoleEventArgs ev)
- {
- if (!ev.Player.IsHost && ev.NewRole == RoleTypeId.Spectator && ev.Reason is not SpawnReason.Destroyed && Events.Instance.Config.ShouldDropInventory)
- ev.Player.Inventory.ServerDropEverything();
- }
-
///
public static void OnSpawningRagdoll(SpawningRagdollEventArgs ev)
{
@@ -104,7 +97,7 @@ public static void OnActivatingSense(ActivatingSenseEventArgs ev)
{
if (ev.Target is null)
return;
- if ((Events.Instance.Config.CanScp049SenseTutorial || ev.Target.Role.Type is not RoleTypeId.Tutorial) && !Scp049Role.TurnedPlayers.Contains(ev.Target))
+ if (!Scp049Role.TurnedPlayers.Contains(ev.Target))
return;
ev.Target = ev.Scp049.SenseAbility.CanFindTarget(out ReferenceHub hub) ? Player.Get(hub) : null;
}
diff --git a/EXILED/Exiled.Events/Patches/Events/Map/ExplodingFlashGrenade.cs b/EXILED/Exiled.Events/Patches/Events/Map/ExplodingFlashGrenade.cs
index 88549bc13a..f376123a9e 100644
--- a/EXILED/Exiled.Events/Patches/Events/Map/ExplodingFlashGrenade.cs
+++ b/EXILED/Exiled.Events/Patches/Events/Map/ExplodingFlashGrenade.cs
@@ -26,7 +26,7 @@ namespace Exiled.Events.Patches.Events.Map
///
/// Patches .
- /// Adds the event and .
+ /// Adds the event"/>.
///
[HarmonyPatch(typeof(FlashbangGrenade), nameof(FlashbangGrenade.ServerFuseEnd))]
internal static class ExplodingFlashGrenade
@@ -69,10 +69,8 @@ private static void ProcessEvent(FlashbangGrenade instance, float distance)
if ((instance.transform.position - player.Position).sqrMagnitude > distance)
continue;
- if (!ExiledEvents.Instance.Config.CanFlashbangsAffectThrower && instance.PreviousOwner.CompareLife(player.ReferenceHub))
- continue;
-
- if (!IndividualFriendlyFire.CheckFriendlyFirePlayer(instance.PreviousOwner, player.ReferenceHub) && !instance.PreviousOwner.CompareLife(player.ReferenceHub))
+ // LifeIdentifier check is needed to fix NW Bug https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/2811
+ if (!IndividualFriendlyFire.CheckFriendlyFirePlayer(instance.PreviousOwner, player.ReferenceHub) && instance.PreviousOwner.LifeIdentifier == player.Footprint.LifeIdentifier)
continue;
if (Physics.Linecast(instance.transform.position, player.CameraTransform.position, instance.BlindingMask))
diff --git a/EXILED/Exiled.Events/Patches/Events/Scp0492/TriggeringBloodlustEvent.cs b/EXILED/Exiled.Events/Patches/Events/Scp0492/TriggeringBloodlustEvent.cs
index 32cd7264d0..722e94f1e6 100644
--- a/EXILED/Exiled.Events/Patches/Events/Scp0492/TriggeringBloodlustEvent.cs
+++ b/EXILED/Exiled.Events/Patches/Events/Scp0492/TriggeringBloodlustEvent.cs
@@ -12,6 +12,7 @@ namespace Exiled.Events.Patches.Events.Scp0492
using Exiled.API.Features;
using Exiled.API.Features.Pools;
+ using Exiled.API.Features.Roles;
using Exiled.Events.Attributes;
using Exiled.Events.EventArgs.Scp0492;
@@ -38,17 +39,31 @@ private static IEnumerable Transpiler(IEnumerable), nameof(HashSet.Contains))),
+ new(OpCodes.Brfalse_S, continueLabel),
});
for (int z = 0; z < newInstructions.Count; z++)
diff --git a/EXILED/Exiled.Events/Patches/Generic/CanScp049SenseTutorial.cs b/EXILED/Exiled.Events/Patches/Generic/CanScp049SenseTutorial.cs
index 01d7daaee5..222d4ecd73 100644
--- a/EXILED/Exiled.Events/Patches/Generic/CanScp049SenseTutorial.cs
+++ b/EXILED/Exiled.Events/Patches/Generic/CanScp049SenseTutorial.cs
@@ -20,11 +20,9 @@ namespace Exiled.Events.Patches.Generic
using static HarmonyLib.AccessTools;
- using ExiledEvents = Exiled.Events.Events;
-
///
/// Patches .
- /// .
+ /// .
///
[HarmonyPatch(typeof(Scp049SenseAbility), nameof(Scp049SenseAbility.CanFindTarget))]
internal static class CanScp049SenseTutorial
@@ -36,7 +34,6 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Brfalse);
Label continueLabel = (Label)newInstructions[index].operand;
- Label skip = generator.DefineLabel();
index += 1;
@@ -46,22 +43,8 @@ private static IEnumerable Transpiler(IEnumerable), nameof(Plugin.Config))),
- new(OpCodes.Callvirt, PropertyGetter(typeof(Config), nameof(Config.CanScp049SenseTutorial))),
- new(OpCodes.Brfalse_S, continueLabel),
-
// if (Scp049Role.TurnedPlayers.Contains(Player.Get(referenceHub)))
- new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(API.Features.Roles.Scp049Role), nameof(API.Features.Roles.Scp049Role.TurnedPlayers))).WithLabels(skip),
+ new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(API.Features.Roles.Scp049Role), nameof(API.Features.Roles.Scp049Role.TurnedPlayers))),
new(OpCodes.Ldloc_S, 6),
new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
new(OpCodes.Callvirt, Method(typeof(HashSet), nameof(HashSet.Contains))),
diff --git a/EXILED/Exiled.Events/Patches/Generic/ParseVisionInformation.cs b/EXILED/Exiled.Events/Patches/Generic/ParseVisionInformation.cs
index 42e6c1013d..86b2bd91de 100644
--- a/EXILED/Exiled.Events/Patches/Generic/ParseVisionInformation.cs
+++ b/EXILED/Exiled.Events/Patches/Generic/ParseVisionInformation.cs
@@ -38,37 +38,16 @@ private static IEnumerable Transpiler(IEnumerable), nameof(Plugin.Config))),
- new(OpCodes.Callvirt, PropertyGetter(typeof(Config), nameof(Config.CanTutorialTriggerScp096))),
- new(OpCodes.Brfalse_S, returnLabel),
-
- // || Scp096Role.TurnedPlayers.Contains(Player.Get(referenceHub))
- new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(Scp096Role), nameof(Scp096Role.TurnedPlayers))).WithLabels(secondCheckPointer),
+ // if (!Scp096Role.TurnedPlayers.Contains(Player.Get(referenceHub)))
+ new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(Scp096Role), nameof(Scp096Role.TurnedPlayers))),
new(OpCodes.Ldarg_1),
new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
new(OpCodes.Callvirt, Method(typeof(HashSet), nameof(HashSet.Contains))),
diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp079Scan.cs b/EXILED/Exiled.Events/Patches/Generic/Scp079Scan.cs
index 6b4b93e406..b3bd692607 100644
--- a/EXILED/Exiled.Events/Patches/Generic/Scp079Scan.cs
+++ b/EXILED/Exiled.Events/Patches/Generic/Scp079Scan.cs
@@ -41,20 +41,6 @@ private static IEnumerable Transpiler(IEnumerable), nameof(Plugin.Config))),
- new(OpCodes.Callvirt, PropertyGetter(typeof(Config), nameof(Config.TutorialNotAffectedByScp079Scan))),
- new(OpCodes.Brfalse_S, returnLabel),
-
// if (Scp079Role.TurnedPlayers.Contains(Player.Get(referenceHub)))
new CodeInstruction(OpCodes.Call, PropertyGetter(typeof(API.Features.Roles.Scp079Role), nameof(API.Features.Roles.Scp079Role.TurnedPlayers))).WithLabels(skip),
new(OpCodes.Ldarg_1),
diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp173BeingLooked.cs b/EXILED/Exiled.Events/Patches/Generic/Scp173BeingLooked.cs
index aadad0c087..763b23f88c 100644
--- a/EXILED/Exiled.Events/Patches/Generic/Scp173BeingLooked.cs
+++ b/EXILED/Exiled.Events/Patches/Generic/Scp173BeingLooked.cs
@@ -69,7 +69,7 @@ private static IEnumerable Transpiler(IEnumerable
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Utility.Commands
+{
+ using System;
+ using System.Linq;
+
+ using CommandSystem;
+ using Exiled.API.Enums;
+ using Exiled.API.Features;
+
+ ///
+ /// Command for spawning .
+ ///
+ [CommandHandler(typeof(RemoteAdminCommandHandler))]
+ public class SpawnPrefabType : ICommand, IUsageProvider
+ {
+ ///
+ public string Command { get; } = "PrefabType";
+
+ ///
+ public string[] Aliases { get; } = Array.Empty();
+
+ ///
+ public string Description { get; } = "Spawning PrefabType";
+
+ ///
+ public string[] Usage { get; } = new string[] { $"{string.Join(", ", EnumUtils.Names)}", };
+
+ ///
+ public bool Execute(ArraySegment arguments, ICommandSender sender, out string response)
+ {
+ if (!sender.CheckPermission(PlayerPermissions.FacilityManagement, out response))
+ {
+ return false;
+ }
+
+ Player.TryGet(sender, out Player player);
+ if (player is null)
+ {
+ response = "You must be a player to use this command.";
+ return false;
+ }
+
+ if (arguments.Count < 1)
+ {
+ response = $"Please, use: {Command} {string.Join(", ", EnumUtils.Names)}";
+ return false;
+ }
+
+ if (!Enum.TryParse(arguments.At(0), out PrefabType prefabType))
+ {
+ response = $"\"{arguments.At(0)}\" is not a valid prefab type.";
+ return false;
+ }
+
+ PrefabHelper.Spawn(prefabType, player.Position, player.Rotation);
+
+ response = "Spawning prefab type...";
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Commands/TpsCommand.cs b/EXILED/Exiled.Utility/Commands/TpsCommand.cs
similarity index 97%
rename from EXILED/Exiled.Events/Commands/TpsCommand.cs
rename to EXILED/Exiled.Utility/Commands/TpsCommand.cs
index 14c0def05e..dcbe781824 100644
--- a/EXILED/Exiled.Events/Commands/TpsCommand.cs
+++ b/EXILED/Exiled.Utility/Commands/TpsCommand.cs
@@ -5,7 +5,7 @@
//
// -----------------------------------------------------------------------
-namespace Exiled.Events.Commands
+namespace Exiled.Utility.Commands
{
using System;
diff --git a/EXILED/Exiled.Utility/Config.cs b/EXILED/Exiled.Utility/Config.cs
new file mode 100644
index 0000000000..2d3bccebab
--- /dev/null
+++ b/EXILED/Exiled.Utility/Config.cs
@@ -0,0 +1,60 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Utility
+{
+ using System.Collections.Generic;
+ using System.ComponentModel;
+
+ using API.Interfaces;
+ using Decals;
+ using Exiled.Utility.Enums;
+ using PlayerRoles;
+
+ ///
+ public sealed class Config : IConfig
+ {
+ ///
+ public bool IsEnabled { get; set; } = true;
+
+ ///
+ public bool Debug { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether SCP-173 can be blocked by the tutorial.
+ ///
+ [Description("Indicates whether RoleTypeId or CustomRole(Name or Id) prevented behaviours")]
+ public Dictionary NewStuffThatWasAboutTutorial { get; set; } = new()
+ {
+ { RoleTypeId.Tutorial.ToString(), NewEnumForAllStuffThatWasAboutTutorial.None },
+ };
+
+ ///
+ /// Gets or sets a value indicating whether flashbangs flash original thrower.
+ ///
+ [Description("Indicates whether flashbangs flash original thrower.")]
+ public bool CanFlashbangsAffectThrower { get; set; } = false;
+
+ ///
+ /// Gets or sets a value indicating whether the inventory should be dropped before being set as spectator, through commands or plugins.
+ ///
+ [Description("Indicates whether the inventory should be dropped before being set as spectator, through commands or plugins")]
+ public bool ShouldDropInventory { get; set; } = false;
+
+ ///
+ /// Gets or sets a value indicating whether Decal spawned.
+ ///
+ [Description("Indicates whether the Decal (Blood, Bullet, Buckshot, GlassCrack) can be spawned")]
+ public List PreventDecalSpawn { get; set; } = new() { };
+
+ ///
+ /// Gets or sets a value indicating whether the SCP079 will recontained if there are no SCPs left.
+ ///
+ [Description("Indicates whether the SCP079 will recontained if there are no SCPs left.")]
+ public bool RecontainScp079IfNoScpsLeft { get; set; } = true;
+ }
+}
diff --git a/EXILED/Exiled.Utility/Enums/NewEnumForAllStuffThatWasAboutTutorial.cs b/EXILED/Exiled.Utility/Enums/NewEnumForAllStuffThatWasAboutTutorial.cs
new file mode 100644
index 0000000000..d16b35ac30
--- /dev/null
+++ b/EXILED/Exiled.Utility/Enums/NewEnumForAllStuffThatWasAboutTutorial.cs
@@ -0,0 +1,53 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Utility.Enums
+{
+ using System;
+
+ ///
+ /// All available screen aspect ratio types.
+ ///
+ [Flags]
+ public enum NewEnumForAllStuffThatWasAboutTutorial
+ {
+ ///
+ /// All.
+ ///
+ All = -1,
+
+ ///
+ /// None.
+ ///
+ None = 0,
+
+ ///
+ /// Prevent from blocking Scp173.
+ ///
+ CanBlockScp173 = 1,
+
+ ///
+ /// Prevent from being target of Scp096.
+ ///
+ CanTriggerScp096 = 2,
+
+ ///
+ /// Prevent from being target of Scp049 and stagger Scp0492.
+ ///
+ CanScp049Sense = 4,
+
+ ///
+ /// Prevent from being target of Scp049 and stagger Scp0492.
+ ///
+ CanScp0492Sense = 4,
+
+ ///
+ /// Prevent Player to be Alarmed by Scp079.
+ ///
+ NotAffectedByScp079Scan = 16,
+ }
+}
diff --git a/EXILED/Exiled.Utility/EventHandler.cs b/EXILED/Exiled.Utility/EventHandler.cs
new file mode 100644
index 0000000000..7f1e958720
--- /dev/null
+++ b/EXILED/Exiled.Utility/EventHandler.cs
@@ -0,0 +1,112 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Utility
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading.Tasks;
+
+ using Decals;
+ using Exiled.API.Enums;
+ using Exiled.API.Features;
+ using Exiled.API.Features.Roles;
+ using Exiled.CustomRoles.API.Features;
+ using Exiled.Events.EventArgs.Map;
+ using Exiled.Events.EventArgs.Player;
+ using Exiled.Utility.Enums;
+ using InventorySystem;
+ using InventorySystem.Items.Firearms.Modules;
+ using PlayerRoles;
+
+#pragma warning disable CS1591 // Commentaire XML manquant pour le type ou le membre visible publiquement
+#pragma warning disable SA1600 // Elements should be documented
+ public class EventHandler
+ {
+ public Config Config { get; internal set; }
+
+ public void OnWaitingForPlayers()
+ {
+ if (!Config.RecontainScp079IfNoScpsLeft)
+ PlayerRoleManager.OnServerRoleSet -= Recontainer.Base.OnServerRoleChanged;
+ }
+
+ ///
+ public void OnChangingRole(ChangingRoleEventArgs ev)
+ {
+ if (!ev.Player.IsHost && ev.NewRole == RoleTypeId.Spectator && ev.Reason is not SpawnReason.Destroyed && Config.ShouldDropInventory)
+ ev.Player.Inventory.ServerDropEverything();
+ }
+
+ public void OnSpawned(SpawnedEventArgs ev)
+ {
+ Scp173Role.TurnedPlayers.Remove(ev.Player);
+ Scp096Role.TurnedPlayers.Remove(ev.Player);
+ Scp049Role.TurnedPlayers.Remove(ev.Player);
+ Scp0492Role.TurnedPlayers.Remove(ev.Player);
+ Scp079Role.TurnedPlayers.Remove(ev.Player);
+ string role = ev.Player.Role.Type.ToString();
+ foreach (CustomRole customRole in CustomRole.Registered)
+ {
+ if (customRole.Check(ev.Player))
+ {
+ role = customRole.Name;
+ break;
+ }
+ }
+
+ if (Config.NewStuffThatWasAboutTutorial.TryGetValue(role, out NewEnumForAllStuffThatWasAboutTutorial bruh))
+ {
+ if (bruh.HasFlag(NewEnumForAllStuffThatWasAboutTutorial.CanBlockScp173))
+ {
+ Scp173Role.TurnedPlayers.Add(ev.Player);
+ }
+
+ if (bruh.HasFlag(NewEnumForAllStuffThatWasAboutTutorial.CanTriggerScp096))
+ {
+ Scp096Role.TurnedPlayers.Add(ev.Player);
+ }
+
+ if (bruh.HasFlag(NewEnumForAllStuffThatWasAboutTutorial.CanScp049Sense))
+ {
+ Scp049Role.TurnedPlayers.Add(ev.Player);
+ }
+
+ if (bruh.HasFlag(NewEnumForAllStuffThatWasAboutTutorial.CanScp0492Sense))
+ {
+ Scp0492Role.TurnedPlayers.Add(ev.Player);
+ }
+
+ if (bruh.HasFlag(NewEnumForAllStuffThatWasAboutTutorial.NotAffectedByScp079Scan))
+ {
+ Scp079Role.TurnedPlayers.Add(ev.Player);
+ }
+ }
+ }
+
+ public void OnPlacingBulletHole(PlacingBulletHoleEventArgs ev)
+ {
+ ImpactEffectsModule impactEffectsModule = ev.Firearm.HitscanHitregModule._impactEffectsModule;
+ int? num = null;
+ for (int i = 0; i < impactEffectsModule.AttachmentOverrides.Length; i++)
+ {
+ if (impactEffectsModule.AttachmentOverrides[i].GetEnabled(ev.Firearm.Base))
+ {
+ num = new int?(i);
+ break;
+ }
+ }
+
+ DecalPoolType decalPoolType = ev.Firearm.HitscanHitregModule._impactEffectsModule.GetSettings(num).BulletholeDecal;
+
+ if (Config.PreventDecalSpawn.Contains(decalPoolType))
+ ev.IsAllowed = false;
+ }
+ }
+}
diff --git a/EXILED/Exiled.Utility/Exiled.Utility.csproj b/EXILED/Exiled.Utility/Exiled.Utility.csproj
new file mode 100644
index 0000000000..fb8692eb27
--- /dev/null
+++ b/EXILED/Exiled.Utility/Exiled.Utility.csproj
@@ -0,0 +1,57 @@
+
+
+
+
+ Library
+ Exiled.Utility
+ true
+ Debug;Release;Installer
+ AnyCPU
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ if not "$(EXILED_DEV_REFERENCES)"=="" copy /y "$(OutputPath)$(AssemblyName).dll" "$(EXILED_DEV_REFERENCES)\Plugins\"
+
+
+ if [[ ! -z "$EXILED_DEV_REFERENCES" ]]; then cp "$(OutputPath)$(AssemblyName).dll" "$EXILED_DEV_REFERENCES/Plugins/"; fi
+
+
+
diff --git a/EXILED/Exiled.Events/Patches/Generic/Scp079Recontain.cs b/EXILED/Exiled.Utility/Patches/Scp079Recontain.cs
similarity index 91%
rename from EXILED/Exiled.Events/Patches/Generic/Scp079Recontain.cs
rename to EXILED/Exiled.Utility/Patches/Scp079Recontain.cs
index f92a59482f..3ce9e801dc 100644
--- a/EXILED/Exiled.Events/Patches/Generic/Scp079Recontain.cs
+++ b/EXILED/Exiled.Utility/Patches/Scp079Recontain.cs
@@ -5,7 +5,7 @@
//
// -----------------------------------------------------------------------
-namespace Exiled.Events.Patches.Generic
+namespace Exiled.Utility.Patches
{
using System.Collections.Generic;
using System.Reflection.Emit;
@@ -37,8 +37,8 @@ private static IEnumerable Transpiler(IEnumerable
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Utility
+{
+ using System;
+
+ using API.Enums;
+ using Exiled.API.Features;
+ using HarmonyLib;
+
+ ///
+ /// Patch and unpatch events into the game.
+ ///
+ public sealed class Utility : Plugin
+ {
+ ///
+ /// Gets the plugin instance.
+ ///
+ public static Utility Instance { get; private set; }
+
+ ///
+ /// Gets the eventHandler.
+ ///
+ public static EventHandler EventHandler { get; private set; }
+
+ ///
+ /// Gets the Harmony instance.
+ ///
+ public static Harmony Harmony { get; private set; }
+
+ ///
+ public override PluginPriority Priority { get; } = PluginPriority.Higher;
+
+ ///
+ public override void OnEnabled()
+ {
+ Instance = this;
+ EventHandler = new EventHandler
+ {
+ Config = Config,
+ };
+ Events.Handlers.Player.ChangingRole += EventHandler.OnChangingRole;
+ Events.Handlers.Server.WaitingForPlayers += EventHandler.OnWaitingForPlayers;
+ Events.Handlers.Map.PlacingBulletHole += EventHandler.OnPlacingBulletHole;
+
+ // BloodDecal is Missing
+ Patch();
+ base.OnEnabled();
+ }
+
+ ///
+ public override void OnDisabled()
+ {
+ base.OnDisabled();
+ Events.Handlers.Player.ChangingRole += EventHandler.OnChangingRole;
+ Events.Handlers.Server.WaitingForPlayers += EventHandler.OnWaitingForPlayers;
+ Events.Handlers.Map.PlacingBulletHole += EventHandler.OnPlacingBulletHole;
+ Unpatch();
+ }
+
+ ///
+ /// Patches all events.
+ ///
+ public void Patch()
+ {
+ Harmony = new Harmony($"com.{nameof(Utility)}.ExiledTeam-{DateTime.Now.Ticks}");
+ GlobalPatchProcessor.PatchAll(Harmony, out int failedPatch);
+ if (failedPatch != 0)
+ Log.Error($"Patching failed! There are {failedPatch} broken patches.");
+ }
+
+ ///
+ /// Unpatches all events.
+ ///
+ public void Unpatch()
+ {
+ Harmony.UnpatchAll(Harmony.Id);
+ Harmony = null;
+ }
+ }
+}
diff --git a/EXILED/build.ps1 b/EXILED/build.ps1
index 2b4f5a54c8..07b30ee294 100644
--- a/EXILED/build.ps1
+++ b/EXILED/build.ps1
@@ -9,6 +9,7 @@ $Projects = @(
'Exiled.API',
'Exiled.Permissions',
'Exiled.Events',
+ 'Exiled.Utility',
'Exiled.CreditTags',
'Exiled.Example',
'Exiled.CustomItems',