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.