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