From 0db86575a0a11a361656f33662fbc11500717bc2 Mon Sep 17 00:00:00 2001 From: Saskyc Date: Fri, 27 Mar 2026 09:40:14 +0100 Subject: [PATCH 1/5] Dummy useful actions and item having dummy emulator --- .../Extensions/DummyActionExtensions.cs | 17 +++ EXILED/Exiled.API/Features/Items/Firearm.cs | 3 +- EXILED/Exiled.API/Features/Items/Item.cs | 17 +++ EXILED/Exiled.API/Features/Npc.cs | 128 ++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 EXILED/Exiled.API/Extensions/DummyActionExtensions.cs diff --git a/EXILED/Exiled.API/Extensions/DummyActionExtensions.cs b/EXILED/Exiled.API/Extensions/DummyActionExtensions.cs new file mode 100644 index 0000000000..283604fbfc --- /dev/null +++ b/EXILED/Exiled.API/Extensions/DummyActionExtensions.cs @@ -0,0 +1,17 @@ +namespace Exiled.API.Extensions +{ + using NetworkManagerUtils.Dummies; + + /// + /// A set of extensions for . + /// + public static class DummyActionExtensions + { + public static DummyAction? FindAction(string name, string parent) + { + DummyAction? dummyAction = null; + bool reachedParent = false; + foreach´(DummyAction action in DummyActionCollector.ServerGetActions()) + } + } +} diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 56215591af..52c668bcd6 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -25,7 +25,7 @@ namespace Exiled.API.Features.Items using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; using InventorySystem.Items.Firearms.Modules; - + using NetworkManagerUtils.Dummies; using static InventorySystem.Items.Firearms.Modules.AnimatorReloaderModuleBase; using BaseFirearm = InventorySystem.Items.Firearms.Firearm; @@ -54,6 +54,7 @@ public Firearm(BaseFirearm itemBase) : base(itemBase) { Base = itemBase; + DummyEmulator = itemBase.DummyEmulator; foreach (ModuleBase module in Base.Modules) { diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index 89bb189c42..68221c3609 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -34,6 +34,7 @@ namespace Exiled.API.Features.Items using InventorySystem.Items.Usables.Scp1576; using InventorySystem.Items.Usables.Scp244; using InventorySystem.Items.Usables.Scp330; + using NetworkManagerUtils.Dummies; using UnityEngine; using BaseConsumable = InventorySystem.Items.Usables.Consumable; @@ -190,6 +191,22 @@ public ushort Serial /// public Player Owner => Player.Get(Base.Owner) ?? Server.Host; + /// + /// Gets the emulator for dummy actions if the item is Autosync. + /// + public DummyKeyEmulator DummyEmulator + { + get + { + if (Base is AutosyncItem item) + { + return item.DummyEmulator; + } + + return null; + } + } + /// /// Gets or sets a reason for adding this item to the inventory. /// diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs index 7791218042..077f6d18af 100644 --- a/EXILED/Exiled.API/Features/Npc.cs +++ b/EXILED/Exiled.API/Features/Npc.cs @@ -16,13 +16,19 @@ namespace Exiled.API.Features using CommandSystem.Commands.RemoteAdmin.Dummies; using Exiled.API.Enums; using Exiled.API.Features.CustomStats; + using Exiled.API.Features.Items; using Exiled.API.Features.Roles; using Footprinting; + using InventorySystem.Items.Autosync; + using InventorySystem.Items.Usables; + using InventorySystem.Items.Usables.Scp330; using MEC; using Mirror; using NetworkManagerUtils.Dummies; using PlayerRoles; + using PlayerRoles.Subroutines; using PlayerStatsSystem; + using RelativePositioning; using UnityEngine; /// @@ -355,5 +361,127 @@ public void LateDestroy(float time) this?.Destroy(); }); } + + public bool MoveRelative(Vector3 dir, float distance) + { + if (Role is not FpcRole fpcRole) + { + return false; + } + + Vector3 vector = ReferenceHub.PlayerCameraReference.TransformDirection(dir).NormalizeIgnoreY(); + fpcRole.FirstPersonController.FpcModule.Motor.ReceivedPosition = new RelativePosition(Position + vector * distance); + return true; + } + + public bool LookHorizontal(float amount) + { + if (Role is not FpcRole fpcRole) + { + return false; + } + + fpcRole.FirstPersonController.FpcModule.MouseLook.CurrentHorizontal += amount; + return true; + } + + public bool LookVertical(float amount) + { + if (Role is not FpcRole fpcRole) + { + return false; + } + + fpcRole.FirstPersonController.FpcModule.MouseLook.CurrentVertical += amount; + return true; + } + + public bool EatCandy(CandyKindID candyKind) + { + foreach(Item? item in Items) + { + if (item is not Scp330 scp330) + { + continue; + } + + return EatCandy(scp330, candyKind); + } + + return false; + } + + public bool EatCandy(Scp330 from, CandyKindID candyKind) + { + for (int i = 0; i < from.Candies.Count; i++) + { + if (from.Base.Candies[i] == candyKind) + { + from.Base.ServerSelectCandy(i); + return true; + } + } + + return false; + } + + /// + /// Gets actions that can be done by the Dummy. + /// + /// + public IReadOnlyList GetActions() + { + return DummyActionCollector.ServerGetActions(ReferenceHub); + } + + public bool RunItemAction(Item item, ActionName name, bool isClick = true) + { + if (item == null) + { + return false; + } + + DummyKeyEmulator emulator = item.DummyEmulator; + if (emulator == null) + { + return false; + } + + emulator.AddEntry(name, isClick); + return true; + } + + public bool StopItemAction(Item item, ActionName name) + { + if (item == null) + { + return false; + } + + DummyKeyEmulator emulator = item.DummyEmulator; + if (emulator == null) + { + return false; + } + + emulator.RemoveEntry(name); + return true; + } + + public bool IsBeingDone(Item item, ActionName name) + { + if (item == null) + { + return false; + } + + DummyKeyEmulator emulator = item.DummyEmulator; + if (emulator == null) + { + return false; + } + + return emulator.GetAction(name, false); + } } } From 293692826946aa86bc6ddb4142ba69085b7bd91e Mon Sep 17 00:00:00 2001 From: Saskyc Date: Fri, 27 Mar 2026 21:44:20 +0100 Subject: [PATCH 2/5] Npc is free to walk now & Firearm doesn't require dummy emulator anymore when the item itself has it. --- .../Extensions/DummyActionExtensions.cs | 17 - EXILED/Exiled.API/Features/Items/Firearm.cs | 1 - EXILED/Exiled.API/Features/Npc.cs | 296 ++++++++++++++++-- 3 files changed, 273 insertions(+), 41 deletions(-) delete mode 100644 EXILED/Exiled.API/Extensions/DummyActionExtensions.cs diff --git a/EXILED/Exiled.API/Extensions/DummyActionExtensions.cs b/EXILED/Exiled.API/Extensions/DummyActionExtensions.cs deleted file mode 100644 index 283604fbfc..0000000000 --- a/EXILED/Exiled.API/Extensions/DummyActionExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Exiled.API.Extensions -{ - using NetworkManagerUtils.Dummies; - - /// - /// A set of extensions for . - /// - public static class DummyActionExtensions - { - public static DummyAction? FindAction(string name, string parent) - { - DummyAction? dummyAction = null; - bool reachedParent = false; - foreach´(DummyAction action in DummyActionCollector.ServerGetActions()) - } - } -} diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 52c668bcd6..0ac20c9281 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -54,7 +54,6 @@ public Firearm(BaseFirearm itemBase) : base(itemBase) { Base = itemBase; - DummyEmulator = itemBase.DummyEmulator; foreach (ModuleBase module in Base.Modules) { diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs index 077f6d18af..ed7ac04250 100644 --- a/EXILED/Exiled.API/Features/Npc.cs +++ b/EXILED/Exiled.API/Features/Npc.cs @@ -19,13 +19,14 @@ namespace Exiled.API.Features using Exiled.API.Features.Items; using Exiled.API.Features.Roles; using Footprinting; - using InventorySystem.Items.Autosync; + using InventorySystem; using InventorySystem.Items.Usables; using InventorySystem.Items.Usables.Scp330; using MEC; using Mirror; using NetworkManagerUtils.Dummies; using PlayerRoles; + using PlayerRoles.FirstPersonControl; using PlayerRoles.Subroutines; using PlayerStatsSystem; using RelativePositioning; @@ -53,6 +54,16 @@ public Npc(GameObject gameObject) { } + /// + /// Defines if Npc is set up by Exiled . + /// + private bool exiledSpawned = true; + + /// + /// Delayed handles used in so we don't have 2 recursive loops. + /// + private CoroutineHandle? setupHandle; + /// /// Gets a list of Npcs. /// @@ -273,6 +284,52 @@ public static Npc Spawn(string name, RoleTypeId role, Vector3 position) return npc; } + /// + /// Setups the Npc frame by frame. + /// + /// The type of role Npc will have. + /// The position Npc has. + public void Setup(RoleTypeId role, Vector3 position, RoleSpawnFlags spawnFlags = RoleSpawnFlags.All) + { + if (setupHandle.HasValue) + { + Timing.KillCoroutines(setupHandle.Value); + } + + exiledSpawned = false; + if (Role.Type == role && Vector3.Distance(Position, position) <= 1 && MaxHealth != 0 && Health == MaxHealth) + { + exiledSpawned = true; + if (setupHandle.HasValue) + { + Timing.KillCoroutines(setupHandle.Value); + } + + return; + } + + if (Role.Type != role) + { + Role.Set(role, SpawnReason.ForceClass, spawnFlags); + setupHandle = Timing.CallDelayed(Timing.WaitForOneFrame, () => Setup(role, position)); + return; + } + + if (Vector3.Distance(Position, position) > 1) + { + Position = position; + setupHandle = Timing.CallDelayed(Timing.WaitForOneFrame, () => Setup(role, position)); + return; + } + + if (Health != MaxHealth) + { + Health = MaxHealth; + setupHandle = Timing.CallDelayed(Timing.WaitForOneFrame, () => Setup(role, position)); + return; + } + } + /// /// Spawns an NPC based on the given parameters. /// @@ -362,6 +419,13 @@ public void LateDestroy(float time) }); } + /// + /// Moves Npc by direction times distance. + /// For future maintainer: implements . + /// + /// Direction where Npc should move. + /// The distance that the Npc should move by. + /// true if moved. public bool MoveRelative(Vector3 dir, float distance) { if (Role is not FpcRole fpcRole) @@ -374,6 +438,11 @@ public bool MoveRelative(Vector3 dir, float distance) return true; } + /// + /// Makes the Npc look horizontal. Right or Left. + /// + /// Amount that will be added to horizontal. + /// True if successful. public bool LookHorizontal(float amount) { if (Role is not FpcRole fpcRole) @@ -385,6 +454,11 @@ public bool LookHorizontal(float amount) return true; } + /// + /// Makes the Npc look vertical. Up or Down. + /// + /// Amount that will be added to vertical. + /// True if successful. public bool LookVertical(float amount) { if (Role is not FpcRole fpcRole) @@ -396,6 +470,61 @@ public bool LookVertical(float amount) return true; } + /// + /// Forces Npc to look at certain point. + /// + /// Position to look at. + /// The amount in percentage how much to look at the position, 1 is full and will immediately look at point. + /// True if successful. + public bool LookAtPoint(Vector3 position, float lerp = 1) + { + if (Role is not FpcRole fpcRole) + { + return false; + } + + fpcRole.FirstPersonController.LookAtPoint(position, lerp); + return true; + } + + /// + /// Forces Npc to look at certain point. + /// + /// Position to look at. + /// The amount in percentage how much to look at the position, 1 is full and will immediately look at point. + /// True if successful. + public bool LookAtDirection(Vector3 dir, float lerp = 1) + { + if (Role is not FpcRole fpcRole) + { + return false; + } + + fpcRole.FirstPersonController.LookAtDirection(dir, lerp); + return true; + } + + /// + /// Makes the Npc jump by amount of strength. + /// + /// The strength used to jump. Null will choose the default one. + /// True if successful. + public bool Jump(float? jumpStrength = null) + { + if (Role is not FpcRole fpcRole) + { + return false; + } + + fpcRole.Jump(jumpStrength); + return true; + } + + /// + /// Makes the Npc eat certain kind of candy. + /// + /// The kind of candy to eat. + /// True if successful. public bool EatCandy(CandyKindID candyKind) { foreach(Item? item in Items) @@ -411,6 +540,12 @@ public bool EatCandy(CandyKindID candyKind) return false; } + /// + /// Makes the Npc eat certain kind of candy. + /// + /// The bag. + /// The kind of candy to eat. + /// True if successful. public bool EatCandy(Scp330 from, CandyKindID candyKind) { for (int i = 0; i < from.Candies.Count; i++) @@ -428,37 +563,152 @@ public bool EatCandy(Scp330 from, CandyKindID candyKind) /// /// Gets actions that can be done by the Dummy. /// - /// + /// . public IReadOnlyList GetActions() { return DummyActionCollector.ServerGetActions(ReferenceHub); } - public bool RunItemAction(Item item, ActionName name, bool isClick = true) + /// + /// Equips Item. + /// . + /// + /// The to equip. + public void EquipItem(Item item) + { + Inventory?.ServerSelectItem(item.Serial); + } + + /// + /// Equips nothing. + /// . + /// + public void HolsterItem() { - if (item == null) - { - return false; - } + Inventory?.ServerSelectItem(0); + } + + /// + /// Forces the Npc to stop using item. + /// + /// The to cancel usage of. + public void CancelUseItem(Item item) + { + UsableItemsController.ServerEmulateMessage(item.Serial, StatusMessage.StatusType.Cancel); + } + + /// + /// Forces the Npc to shoot. + /// + /// Specifies if the shooting is to be held. + /// True if successful. + public bool Shoot(bool hold) => + RunItemAction(CurrentItem, ActionName.Shoot, hold); + + /// + /// Forces the Npc to reload. + /// + /// Specifies if the reloading is to be held. + /// True if successful. + public bool Reload(bool hold) => + RunItemAction(CurrentItem, ActionName.Reload, hold); + + /// + /// Forces the Npc to zoom. + /// + /// Specifies if the zooming is to be held. + /// True if successful. + public bool Zoom(bool hold) => + RunItemAction(CurrentItem, ActionName.Zoom, hold); + + /// + /// Forces the Npc to run generic action with item. + /// + /// The to run action for. + /// The name of action to force. + /// Specifies if the action is to be held. + /// True if successful. + public bool RunItemAction(Item item, ActionName name, bool hold = true) => + RunAction(item?.DummyEmulator, name, hold); + + /// + /// Forces the Npc to stop generic action with item. + /// + /// The to stop action for. + /// The name of action to stop. + /// True if successful. + public bool StopItemAction(Item item, ActionName name) => + StopAction(item?.DummyEmulator, name); + + /// + /// Checks if certain action is currently active. + /// + /// The to check action for. + /// The name of action to check. + /// True if action is actively executed and emulator is not null. + public bool IsBeingDone(Item item, ActionName name) => + IsBeingDone(item?.DummyEmulator, name); + + /// + /// Forces the Npc to run generic action with subroutine. + /// + /// The to run action for. + /// The name of action to force. + /// Specifies if the action is to be held. + /// . + /// True if successful. + public bool RunSubroutineAction(T subroutine, ActionName name, bool hold = true) + where T : SubroutineBase => + RunAction(subroutine?.DummyEmulator, name, hold); + + /// + /// Forces the Npc to stop generic action with subroutine. + /// + /// The to stop action for. + /// The name of action to stop. + /// . + /// True if successful. + public bool StopSubroutineAction(T subroutine, ActionName name) + where T : SubroutineBase => + StopAction(subroutine?.DummyEmulator, name); + + /// + /// Checks if certain action is currently active. + /// + /// The to check action for. + /// The name of action to check. + /// . + /// True if action is actively executed and emulator is not null. + public bool IsBeingDone(T subroutine, ActionName name) + where T : SubroutineBase => + IsBeingDone(subroutine?.DummyEmulator, name); - DummyKeyEmulator emulator = item.DummyEmulator; + /// + /// Forces the Npc to run generic action with emulator. + /// + /// The to run action for. + /// The name of action to force. + /// Specifies if the action is to be held. + /// True if successful. + public bool RunAction(DummyKeyEmulator? emulator, ActionName name, bool hold) + { if (emulator == null) { return false; } - emulator.AddEntry(name, isClick); + emulator.AddEntry(name, !hold); return true; } - public bool StopItemAction(Item item, ActionName name) + /// + /// Forces the Npc to stop generic action with emulator. + /// + /// The to stop action for. + /// The name of action to stop. + /// True if successful. + public bool StopAction(DummyKeyEmulator? emulator, ActionName name) { - if (item == null) - { - return false; - } - - DummyKeyEmulator emulator = item.DummyEmulator; if (emulator == null) { return false; @@ -468,14 +718,14 @@ public bool StopItemAction(Item item, ActionName name) return true; } - public bool IsBeingDone(Item item, ActionName name) + /// + /// Checks if certain action is currently active. + /// + /// The to check action for. + /// The name of action to check. + /// True if action is actively executed and emulator is not null. + public bool IsBeingDone(DummyKeyEmulator? emulator, ActionName name) { - if (item == null) - { - return false; - } - - DummyKeyEmulator emulator = item.DummyEmulator; if (emulator == null) { return false; From c3bfb27b7a9419aeae4ffd09cf10bd277ef62ed6 Mon Sep 17 00:00:00 2001 From: Saskyc Date: Fri, 27 Mar 2026 21:47:52 +0100 Subject: [PATCH 3/5] Better setupping may be worked on later. --- EXILED/Exiled.API/Features/Npc.cs | 56 ------------------------------- 1 file changed, 56 deletions(-) diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs index ed7ac04250..3650adbc9f 100644 --- a/EXILED/Exiled.API/Features/Npc.cs +++ b/EXILED/Exiled.API/Features/Npc.cs @@ -54,16 +54,6 @@ public Npc(GameObject gameObject) { } - /// - /// Defines if Npc is set up by Exiled . - /// - private bool exiledSpawned = true; - - /// - /// Delayed handles used in so we don't have 2 recursive loops. - /// - private CoroutineHandle? setupHandle; - /// /// Gets a list of Npcs. /// @@ -284,52 +274,6 @@ public static Npc Spawn(string name, RoleTypeId role, Vector3 position) return npc; } - /// - /// Setups the Npc frame by frame. - /// - /// The type of role Npc will have. - /// The position Npc has. - public void Setup(RoleTypeId role, Vector3 position, RoleSpawnFlags spawnFlags = RoleSpawnFlags.All) - { - if (setupHandle.HasValue) - { - Timing.KillCoroutines(setupHandle.Value); - } - - exiledSpawned = false; - if (Role.Type == role && Vector3.Distance(Position, position) <= 1 && MaxHealth != 0 && Health == MaxHealth) - { - exiledSpawned = true; - if (setupHandle.HasValue) - { - Timing.KillCoroutines(setupHandle.Value); - } - - return; - } - - if (Role.Type != role) - { - Role.Set(role, SpawnReason.ForceClass, spawnFlags); - setupHandle = Timing.CallDelayed(Timing.WaitForOneFrame, () => Setup(role, position)); - return; - } - - if (Vector3.Distance(Position, position) > 1) - { - Position = position; - setupHandle = Timing.CallDelayed(Timing.WaitForOneFrame, () => Setup(role, position)); - return; - } - - if (Health != MaxHealth) - { - Health = MaxHealth; - setupHandle = Timing.CallDelayed(Timing.WaitForOneFrame, () => Setup(role, position)); - return; - } - } - /// /// Spawns an NPC based on the given parameters. /// From d62ace72045b3bf0c800d148c3224371abe3fded Mon Sep 17 00:00:00 2001 From: Saskyc Date: Fri, 27 Mar 2026 22:00:29 +0100 Subject: [PATCH 4/5] Stylecop adjustments --- EXILED/Exiled.API/Features/Items/Firearm.cs | 3 +-- EXILED/Exiled.API/Features/Npc.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs index 0ac20c9281..ad49c49804 100644 --- a/EXILED/Exiled.API/Features/Items/Firearm.cs +++ b/EXILED/Exiled.API/Features/Items/Firearm.cs @@ -7,7 +7,6 @@ namespace Exiled.API.Features.Items { - using System; using System.Collections.Generic; using System.Linq; @@ -25,7 +24,7 @@ namespace Exiled.API.Features.Items using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; using InventorySystem.Items.Firearms.Modules; - using NetworkManagerUtils.Dummies; + using static InventorySystem.Items.Firearms.Modules.AnimatorReloaderModuleBase; using BaseFirearm = InventorySystem.Items.Firearms.Firearm; diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs index 3650adbc9f..a6ad0d6842 100644 --- a/EXILED/Exiled.API/Features/Npc.cs +++ b/EXILED/Exiled.API/Features/Npc.cs @@ -378,7 +378,7 @@ public bool MoveRelative(Vector3 dir, float distance) } Vector3 vector = ReferenceHub.PlayerCameraReference.TransformDirection(dir).NormalizeIgnoreY(); - fpcRole.FirstPersonController.FpcModule.Motor.ReceivedPosition = new RelativePosition(Position + vector * distance); + fpcRole.FirstPersonController.FpcModule.Motor.ReceivedPosition = new RelativePosition(Position + (vector * distance)); return true; } From 8e4a18aeebd5ff4db4dd39ebd259d709bfe4b171 Mon Sep 17 00:00:00 2001 From: "@Someone" <45270312+Someone-193@users.noreply.github.com> Date: Thu, 16 Apr 2026 19:25:15 -0400 Subject: [PATCH 5/5] Docs + use better value in certain cases --- EXILED/Exiled.API/Features/Items/Item.cs | 3 +- EXILED/Exiled.API/Features/Npc.cs | 49 +++++++++--------------- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs index 68221c3609..988859049b 100644 --- a/EXILED/Exiled.API/Features/Items/Item.cs +++ b/EXILED/Exiled.API/Features/Items/Item.cs @@ -192,8 +192,9 @@ public ushort Serial public Player Owner => Player.Get(Base.Owner) ?? Server.Host; /// - /// Gets the emulator for dummy actions if the item is Autosync. + /// Gets the emulator for dummy actions if this item inherits . /// + /// Returns null if this item does not inherit . public DummyKeyEmulator DummyEmulator { get diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs index a6ad0d6842..444da0ab5f 100644 --- a/EXILED/Exiled.API/Features/Npc.cs +++ b/EXILED/Exiled.API/Features/Npc.cs @@ -364,51 +364,44 @@ public void LateDestroy(float time) } /// - /// Moves Npc by direction times distance. - /// For future maintainer: implements . + /// Moves this Npc by a direction relative to where they are looking. /// - /// Direction where Npc should move. + /// Direction where Npc should move relative to where they are looking. /// The distance that the Npc should move by. - /// true if moved. - public bool MoveRelative(Vector3 dir, float distance) + /// True if successful. + public bool TryMoveRelative(Vector3 dir, float distance) { - if (Role is not FpcRole fpcRole) - { + if (Role is not FpcRole) return false; - } - Vector3 vector = ReferenceHub.PlayerCameraReference.TransformDirection(dir).NormalizeIgnoreY(); - fpcRole.FirstPersonController.FpcModule.Motor.ReceivedPosition = new RelativePosition(Position + (vector * distance)); + Vector3 vector = CameraTransform.TransformDirection(dir).NormalizeIgnoreY(); + Position += vector * distance; return true; } /// - /// Makes the Npc look horizontal. Right or Left. + /// Makes the Npc look more left or more right. /// - /// Amount that will be added to horizontal. + /// Amount that will be added to horizontal (in degrees). /// True if successful. - public bool LookHorizontal(float amount) + public bool TryAddHorizontalLook(float amount) { if (Role is not FpcRole fpcRole) - { return false; - } fpcRole.FirstPersonController.FpcModule.MouseLook.CurrentHorizontal += amount; return true; } /// - /// Makes the Npc look vertical. Up or Down. + /// Makes the Npc look more up or more down. /// - /// Amount that will be added to vertical. + /// Amount that will be added to vertical (in degrees). /// True if successful. - public bool LookVertical(float amount) + public bool TryAddVerticalLook(float amount) { if (Role is not FpcRole fpcRole) - { return false; - } fpcRole.FirstPersonController.FpcModule.MouseLook.CurrentVertical += amount; return true; @@ -418,31 +411,27 @@ public bool LookVertical(float amount) /// Forces Npc to look at certain point. /// /// Position to look at. - /// The amount in percentage how much to look at the position, 1 is full and will immediately look at point. + /// The amount in percentage how much to look at the position, 1 is full and will immediately look at the point. /// True if successful. - public bool LookAtPoint(Vector3 position, float lerp = 1) + public bool TryLookAtPoint(Vector3 position, float lerp = 1) { if (Role is not FpcRole fpcRole) - { return false; - } fpcRole.FirstPersonController.LookAtPoint(position, lerp); return true; } /// - /// Forces Npc to look at certain point. + /// Forces Npc to look at a certain direction. /// - /// Position to look at. - /// The amount in percentage how much to look at the position, 1 is full and will immediately look at point. + /// The direction to look at. + /// The amount in percentage how much to look at the direction, 1 is full and will immediately look at the target direction. /// True if successful. - public bool LookAtDirection(Vector3 dir, float lerp = 1) + public bool TryLookAtDirection(Vector3 dir, float lerp = 1) { if (Role is not FpcRole fpcRole) - { return false; - } fpcRole.FirstPersonController.LookAtDirection(dir, lerp); return true;