diff --git a/EXILED/Exiled.API/Features/Items/Firearm.cs b/EXILED/Exiled.API/Features/Items/Firearm.cs
index 56215591a..ad49c4980 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;
diff --git a/EXILED/Exiled.API/Features/Items/Item.cs b/EXILED/Exiled.API/Features/Items/Item.cs
index 89bb189c4..988859049 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,23 @@ public ushort Serial
///
public Player Owner => Player.Get(Base.Owner) ?? Server.Host;
+ ///
+ /// Gets the emulator for dummy actions if this item inherits .
+ ///
+ /// Returns null if this item does not inherit .
+ 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 779121804..444da0ab5 100644
--- a/EXILED/Exiled.API/Features/Npc.cs
+++ b/EXILED/Exiled.API/Features/Npc.cs
@@ -16,13 +16,20 @@ 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;
+ 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;
using UnityEngine;
///
@@ -355,5 +362,309 @@ public void LateDestroy(float time)
this?.Destroy();
});
}
+
+ ///
+ /// Moves this Npc by a direction relative to where they are looking.
+ ///
+ /// Direction where Npc should move relative to where they are looking.
+ /// The distance that the Npc should move by.
+ /// True if successful.
+ public bool TryMoveRelative(Vector3 dir, float distance)
+ {
+ if (Role is not FpcRole)
+ return false;
+
+ Vector3 vector = CameraTransform.TransformDirection(dir).NormalizeIgnoreY();
+ Position += vector * distance;
+ return true;
+ }
+
+ ///
+ /// Makes the Npc look more left or more right.
+ ///
+ /// Amount that will be added to horizontal (in degrees).
+ /// True if successful.
+ 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 more up or more down.
+ ///
+ /// Amount that will be added to vertical (in degrees).
+ /// True if successful.
+ public bool TryAddVerticalLook(float amount)
+ {
+ if (Role is not FpcRole fpcRole)
+ return false;
+
+ fpcRole.FirstPersonController.FpcModule.MouseLook.CurrentVertical += 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 the point.
+ /// True if successful.
+ 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 a certain direction.
+ ///
+ /// 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 TryLookAtDirection(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)
+ {
+ if (item is not Scp330 scp330)
+ {
+ continue;
+ }
+
+ return EatCandy(scp330, 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++)
+ {
+ 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);
+ }
+
+ ///
+ /// Equips Item.
+ /// .
+ ///
+ /// The to equip.
+ public void EquipItem(Item item)
+ {
+ Inventory?.ServerSelectItem(item.Serial);
+ }
+
+ ///
+ /// Equips nothing.
+ /// .
+ ///
+ public void HolsterItem()
+ {
+ 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);
+
+ ///
+ /// 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, !hold);
+ return true;
+ }
+
+ ///
+ /// 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 (emulator == null)
+ {
+ return false;
+ }
+
+ emulator.RemoveEntry(name);
+ return true;
+ }
+
+ ///
+ /// 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 (emulator == null)
+ {
+ return false;
+ }
+
+ return emulator.GetAction(name, false);
+ }
}
}