diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs index d7d7d6d78c..4055dfa6a9 100644 --- a/EXILED/Exiled.API/Features/Npc.cs +++ b/EXILED/Exiled.API/Features/Npc.cs @@ -15,6 +15,7 @@ namespace Exiled.API.Features using CentralAuth; using CommandSystem; + using CommandSystem.Commands.RemoteAdmin.Dummies; using Exiled.API.Enums; using Exiled.API.Features.Components; using Exiled.API.Features.Roles; @@ -47,7 +48,7 @@ public Npc(GameObject gameObject) /// /// Gets a list of Npcs. /// - public static new List List => Player.List.OfType().ToList(); + public static new IReadOnlyCollection List => Dictionary.Values.OfType().ToList(); /// /// Gets or sets the player's position. @@ -63,6 +64,113 @@ public override Vector3 Position } } + /// + /// Gets or sets the player being followed. + /// + /// The npc must have . + public Player? FollowedPlayer + { + get => !GameObject.TryGetComponent(out PlayerFollower follower) ? null : Player.Get(follower._hubToFollow); + + set + { + if (!GameObject.TryGetComponent(out PlayerFollower follower)) + { + GameObject.AddComponent()._hubToFollow = value?.ReferenceHub; + return; + } + + follower._hubToFollow = value?.ReferenceHub; + } + } + + /// + /// Gets or sets the Max Distance of the npc. + /// + /// The npc must have . + public float? MaxDistance + { + get + { + if (!GameObject.TryGetComponent(out PlayerFollower follower)) + return null; + + return follower._maxDistance; + } + + set + { + if(!value.HasValue) + return; + + if (!GameObject.TryGetComponent(out PlayerFollower follower)) + { + GameObject.AddComponent()._maxDistance = value.Value; + return; + } + + follower._maxDistance = value.Value; + } + } + + /// + /// Gets or sets the Min Distance of the npc. + /// + /// The npc must have . + public float? MinDistance + { + get + { + if (!GameObject.TryGetComponent(out PlayerFollower follower)) + return null; + + return follower._minDistance; + } + + set + { + if(!value.HasValue) + return; + + if (!GameObject.TryGetComponent(out PlayerFollower follower)) + { + GameObject.AddComponent()._minDistance = value.Value; + return; + } + + follower._minDistance = value.Value; + } + } + + /// + /// Gets or sets the Speed of the npc. + /// + /// The npc must have . + public float? Speed + { + get + { + if (!GameObject.TryGetComponent(out PlayerFollower follower)) + return null; + + return follower._speed; + } + + set + { + if(!value.HasValue) + return; + + if (!GameObject.TryGetComponent(out PlayerFollower follower)) + { + GameObject.AddComponent()._speed = value.Value; + return; + } + + follower._speed = value.Value; + } + } + /// /// Retrieves the NPC associated with the specified ReferenceHub. /// @@ -133,100 +241,24 @@ public override Vector3 Position /// The NPC associated with the NetworkConnection, or null if not found. public static new Npc? Get(NetworkConnection conn) => Player.Get(conn) as Npc; - /// - /// Docs. - /// - /// Docs1. - /// Docs2. - /// Docs3. - /// Docs4. - public static Npc Create(string name, RoleTypeId role, Vector3 position) - { - // TODO: Test this. - Npc npc = new(DummyUtils.SpawnDummy(name)) - { - IsNPC = true, - }; - - npc.Role.Set(role); - npc.Position = position; - - return npc; - } - /// /// Spawns an NPC based on the given parameters. /// /// The name of the NPC. /// The RoleTypeId of the NPC. - /// The player ID of the NPC. - /// The userID of the NPC. - /// The position to spawn the NPC. - /// The spawned. - [Obsolete("This method is marked as obsolete due to a bug that make player have the same id. Use Npc.Spawn(string) instead", true)] - public static Npc Spawn(string name, RoleTypeId role, int id = 0, string userId = PlayerAuthenticationManager.DedicatedId, Vector3? position = null) + /// The position where the NPC should spawn. + /// Docs4. + public static Npc Spawn(string name, RoleTypeId role, Vector3 position) { - GameObject newObject = UnityEngine.Object.Instantiate(Mirror.NetworkManager.singleton.playerPrefab); - - Npc npc = new(newObject) - { - IsNPC = true, - }; - - if (!RecyclablePlayerId.FreeIds.Contains(id) && RecyclablePlayerId._autoIncrement >= id) - { - Log.Warn($"{Assembly.GetCallingAssembly().GetName().Name} tried to spawn an NPC with a duplicate PlayerID. Using auto-incremented ID instead to avoid an ID clash."); - id = new RecyclablePlayerId(true).Value; - } - - 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@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}"); - } - - FakeConnection fakeConnection = new(id); - NetworkServer.AddPlayerForConnection(fakeConnection, newObject); - - npc.ReferenceHub.nicknameSync.Network_myNickSync = name; - Dictionary.Add(newObject, npc); + Npc npc = new(DummyUtils.SpawnDummy(name)); 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; + npc.Role.Set(role); + npc.Position = position; }); + Dictionary.Add(npc.GameObject, npc); return npc; } @@ -236,58 +268,11 @@ public static Npc Spawn(string name, RoleTypeId role, int id = 0, string userId /// 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) + public static Npc Spawn(string name, RoleTypeId role = RoleTypeId.None, bool ignored = false, 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); + Npc npc = new(DummyUtils.SpawnDummy(name)); Timing.CallDelayed(0.5f, () => { @@ -300,16 +285,38 @@ public static Npc Spawn(string name, RoleTypeId role = RoleTypeId.None, bool ign if (ignored) Round.IgnoredPlayers.Add(npc.ReferenceHub); + Dictionary.Add(npc.GameObject, npc); return npc; } /// /// Destroys all NPCs currently spawned. /// - public static void DestroyAll() + public static void DestroyAll() => DummyUtils.DestroyAllDummies(); + + /// + /// Follow a specific player. + /// + /// the Player to follow. + public void Follow(Player player) + { + PlayerFollower follow = !GameObject.TryGetComponent(out PlayerFollower follower) ? GameObject.AddComponent() : follower; + + follow.Init(player.ReferenceHub); + } + + /// + /// Follow a specific player. + /// + /// the Player to follow. + /// the max distance the npc will go. + /// the min distance the npc will go. + /// the speed the npc will go. + public void Follow(Player player, float maxDistance, float minDistance, float speed = 30f) { - foreach (Npc npc in List) - npc.Destroy(); + PlayerFollower follow = !GameObject.TryGetComponent(out PlayerFollower follower) ? GameObject.AddComponent() : follower; + + follow.Init(player.ReferenceHub, maxDistance, minDistance, speed); } /// @@ -320,11 +327,8 @@ public void Destroy() try { Round.IgnoredPlayers.Remove(ReferenceHub); - NetworkConnectionToClient conn = ReferenceHub.connectionToClient; - ReferenceHub.OnDestroy(); - CustomNetworkManager.TypedSingleton.OnServerDisconnect(conn); - Dictionary.Remove(GameObject); - Object.Destroy(GameObject); + Dictionary.Remove(ReferenceHub.gameObject); + NetworkServer.Destroy(ReferenceHub.gameObject); } catch (Exception e) { diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 80a09d4aa7..56408f8aa1 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -135,7 +135,7 @@ public Player(GameObject gameObject) /// /// Gets a list of all 's on the server. /// - public static IReadOnlyCollection List => Dictionary.Values; + public static IReadOnlyCollection List => Dictionary.Values.Where(x => !x.IsNPC).ToList(); /// /// Gets a containing cached and their user ids. @@ -301,9 +301,9 @@ public AuthenticationType AuthenticationType public bool IsVerified { get; internal set; } /// - /// Gets or sets a value indicating whether the player is a NPC. + /// Gets a value indicating whether the player is a NPC. /// - public bool IsNPC { get; set; } + public bool IsNPC => ReferenceHub.IsDummy; /// /// Gets a value indicating whether the player has an active CustomName.