diff --git a/EXILED/Exiled.API/Features/Items/Scp330.cs b/EXILED/Exiled.API/Features/Items/Scp330.cs
index 689929f12b..d0f902184a 100644
--- a/EXILED/Exiled.API/Features/Items/Scp330.cs
+++ b/EXILED/Exiled.API/Features/Items/Scp330.cs
@@ -87,6 +87,11 @@ internal Scp330()
///
public CandyKindID ExposedType { get; set; } = CandyKindID.None;
+ ///
+ /// Gets or sets the candy that will be added to the bag. Used for events.
+ ///
+ internal CandyKindID CandyToAdd { get; set; } = CandyKindID.None;
+
///
/// Adds a specific candy to the bag.
///
diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs
index 82a6c73745..e79038065c 100644
--- a/EXILED/Exiled.API/Features/Npc.cs
+++ b/EXILED/Exiled.API/Features/Npc.cs
@@ -16,7 +16,6 @@ namespace Exiled.API.Features
using CentralAuth;
using CommandSystem;
using Exiled.API.Enums;
- using Exiled.API.Extensions;
using Exiled.API.Features.Components;
using Exiled.API.Features.Roles;
using Footprinting;
@@ -142,6 +141,7 @@ public override Vector3 Position
/// The userID of the NPC.
/// The position to spawn the NPC.
/// The spawned.
+ [Obsolete("This metod is marked as obsolet due to a bug that make player have the same id. Use Npc.Spawn(string) instead")]
public static Npc Spawn(string name, RoleTypeId role, int id = 0, string userId = PlayerAuthenticationManager.DedicatedId, Vector3? position = null)
{
GameObject newObject = UnityEngine.Object.Instantiate(Mirror.NetworkManager.singleton.playerPrefab);
@@ -208,18 +208,118 @@ public static Npc Spawn(string name, RoleTypeId role, int id = 0, string userId
return npc;
}
+ ///
+ /// Spawns an NPC based on the given parameters.
+ ///
+ /// The name of the NPC.
+ /// The RoleTypeId of the NPC, defaulting to None.
+ /// Whether the NPC should be ignored by round ending checks.
+ /// The userID of the NPC for authentication. Defaults to the Dedicated ID.
+ /// The position where the NPC should spawn. If null, the default spawn location is used.
+ /// The spawned.
+ public static Npc Spawn(string name, RoleTypeId role = RoleTypeId.None, bool ignored = false, string userId = PlayerAuthenticationManager.DedicatedId, Vector3? position = null)
+ {
+ GameObject newObject = UnityEngine.Object.Instantiate(Mirror.NetworkManager.singleton.playerPrefab);
+
+ Npc npc = new(newObject)
+ {
+ IsNPC = true,
+ };
+
+ FakeConnection fakeConnection = new(npc.Id);
+
+ try
+ {
+ if (userId == PlayerAuthenticationManager.DedicatedId)
+ {
+ npc.ReferenceHub.authManager.SyncedUserId = userId;
+ try
+ {
+ npc.ReferenceHub.authManager.InstanceMode = ClientInstanceMode.DedicatedServer;
+ }
+ catch (Exception e)
+ {
+ Log.Debug($"Ignore: {e.Message}");
+ }
+ }
+ else
+ {
+ npc.ReferenceHub.authManager.InstanceMode = ClientInstanceMode.Unverified;
+ npc.ReferenceHub.authManager._privUserId = userId == string.Empty ? $"Dummy-{npc.Id}@localhost" : userId;
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Debug($"Ignore: {e.Message}");
+ }
+
+ try
+ {
+ npc.ReferenceHub.roleManager.InitializeNewRole(RoleTypeId.None, RoleChangeReason.None);
+ }
+ catch (Exception e)
+ {
+ Log.Debug($"Ignore: {e.Message}");
+ }
+
+ NetworkServer.AddPlayerForConnection(fakeConnection, newObject);
+
+ npc.ReferenceHub.nicknameSync.Network_myNickSync = name;
+ Dictionary.Add(newObject, npc);
+
+ Timing.CallDelayed(0.5f, () =>
+ {
+ npc.Role.Set(role, SpawnReason.RoundStart, position is null ? RoleSpawnFlags.All : RoleSpawnFlags.AssignInventory);
+
+ if (position is not null)
+ npc.Position = position.Value;
+ });
+
+ if (ignored)
+ Round.IgnoredPlayers.Add(npc.ReferenceHub);
+
+ return npc;
+ }
+
+ ///
+ /// Destroys all NPCs currently spawned.
+ ///
+ public static void DestroyAll()
+ {
+ foreach (Npc npc in List)
+ npc.Destroy();
+ }
+
///
/// Destroys the NPC.
///
public void Destroy()
{
- NetworkConnectionToClient conn = ReferenceHub.connectionToClient;
- if (ReferenceHub._playerId.Value <= RecyclablePlayerId._autoIncrement)
- ReferenceHub._playerId.Destroy();
- ReferenceHub.OnDestroy();
- CustomNetworkManager.TypedSingleton.OnServerDisconnect(conn);
- Dictionary.Remove(GameObject);
- Object.Destroy(GameObject);
+ try
+ {
+ Round.IgnoredPlayers.Remove(ReferenceHub);
+ NetworkConnectionToClient conn = ReferenceHub.connectionToClient;
+ ReferenceHub.OnDestroy();
+ CustomNetworkManager.TypedSingleton.OnServerDisconnect(conn);
+ Dictionary.Remove(GameObject);
+ Object.Destroy(GameObject);
+ }
+ catch (Exception e)
+ {
+ Log.Error($"Error while destroying a NPC: {e.Message}");
+ }
+ }
+
+ ///
+ /// Schedules the destruction of the NPC after a delay.
+ ///
+ /// The delay in seconds before the NPC is destroyed.
+ public void LateDestroy(float time)
+ {
+ Timing.CallDelayed(time, () =>
+ {
+ this?.Destroy();
+ });
}
}
}
diff --git a/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs
index 45a5877559..cd4d4922fd 100644
--- a/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs
+++ b/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs
@@ -31,12 +31,23 @@ public class InteractingScp330EventArgs : IPlayerEvent, IScp330Event, IDeniableE
public InteractingScp330EventArgs(Player player, int usage)
{
Player = player;
- Scp330 = Scp330Bag.TryGetBag(player.ReferenceHub, out Scp330Bag scp330Bag) ? (Scp330)Item.Get(scp330Bag) : null;
- Candy = Scp330Candies.GetRandom();
UsageCount = usage;
ShouldSever = usage >= 2;
ShouldPlaySound = true;
IsAllowed = Player.IsHuman;
+
+ if (Scp330Bag.TryGetBag(player.ReferenceHub, out Scp330Bag scp330Bag))
+ {
+ Scp330 = (Scp330)Item.Get(scp330Bag);
+ }
+ else
+ {
+ Scp330 = (Scp330)Item.Create(ItemType.SCP330, player);
+ Scp330.RemoveAllCandy();
+ player.AddItem(Scp330);
+ }
+
+ Scp330.CandyToAdd = Scp330Candies.GetRandom();
}
///
@@ -47,7 +58,11 @@ public InteractingScp330EventArgs(Player player, int usage)
///
/// Gets or sets a value indicating the type of candy that will be received from this interaction.
///
- public CandyKindID Candy { get; set; }
+ public CandyKindID Candy
+ {
+ get => Scp330.CandyToAdd;
+ set => Scp330.CandyToAdd = value;
+ }
///
/// Gets or sets a value indicating whether the player's hands should get severed.
@@ -71,11 +86,9 @@ public InteractingScp330EventArgs(Player player, int usage)
public Player Player { get; }
///
- /// This value can be null.
public Scp330 Scp330 { get; }
///
- /// This value can be null.
public Item Item => Scp330;
}
}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangingRoleAndSpawned.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangingRoleAndSpawned.cs
index 8de7578477..d1a30dc442 100644
--- a/EXILED/Exiled.Events/Patches/Events/Player/ChangingRoleAndSpawned.cs
+++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangingRoleAndSpawned.cs
@@ -44,6 +44,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable
/// Patches the method to add the
- /// event.
+ /// event.
///
- [EventPatch(typeof(Scp330), nameof(Scp330.InteractingScp330))]
+ [EventPatch(typeof(Handlers.Scp330), nameof(Handlers.Scp330.InteractingScp330))]
[HarmonyPatch(typeof(Scp330Interobject), nameof(Scp330Interobject.ServerInteract))]
public static class InteractingScp330
{
@@ -66,7 +67,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions);
}
}
+
+ ///
+ /// Replaces with .
+ ///
+ [EventPatch(typeof(Handlers.Scp330), nameof(Handlers.Scp330.InteractingScp330))]
+ [HarmonyPatch(typeof(Scp330Bag), nameof(Scp330Bag.TryAddSpecific))]
+ internal static class ReplaceCandy
+ {
+ private static void Prefix(Scp330Bag __instance, ref CandyKindID kind)
+ {
+ Scp330 scp330 = Item.Get(__instance);
+
+ if (scp330.CandyToAdd != CandyKindID.None)
+ kind = scp330.CandyToAdd;
+ }
+ }
}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs b/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs
index d672d18318..587af60556 100644
--- a/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs
+++ b/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs
@@ -84,7 +84,7 @@ private static IEnumerable Transpiler(IEnumerable instruction.StoresField(Field(typeof(CheaterReport), nameof(CheaterReport._lastReport)))) + offset;
diff --git a/EXILED/Exiled.Loader/Loader.cs b/EXILED/Exiled.Loader/Loader.cs
index c174f9c92d..77894ba66f 100644
--- a/EXILED/Exiled.Loader/Loader.cs
+++ b/EXILED/Exiled.Loader/Loader.cs
@@ -394,7 +394,17 @@ public IEnumerator Run(Assembly[] dependencies = null)
GameCore.Version.BackwardCompatibility,
GameCore.Version.BackwardRevision))
{
- ServerConsole.AddLog($"Exiled is outdated, a new version will be installed automatically as soon as it's available.\nSCP:SL: {GameCore.Version.VersionString} Exiled Supported Version: {AutoUpdateFiles.RequiredSCPSLVersion}", ConsoleColor.DarkRed);
+ string messageText = new Version(
+ GameCore.Version.Major,
+ GameCore.Version.Minor,
+ GameCore.Version.Revision) < new Version(
+ AutoUpdateFiles.RequiredSCPSLVersion.Major,
+ AutoUpdateFiles.RequiredSCPSLVersion.Minor,
+ AutoUpdateFiles.RequiredSCPSLVersion.Revision)
+ ? "SCP: SL is outdated. Update SCP: SL Dedicated Server to required version or downgrade Exiled."
+ : "Exiled is outdated, a new version will be installed automatically as soon as it's available.";
+
+ ServerConsole.AddLog($"{messageText}\nSCP:SL version: {GameCore.Version.VersionString} Exiled Supported Version: {AutoUpdateFiles.RequiredSCPSLVersion}", ConsoleColor.DarkRed);
yield break;
}