diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index dd5acffae4..4fe9afb6a1 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -74,6 +74,7 @@
+
diff --git a/Content.Client/GameObjects/Components/IconSmoothing/IconSmoothComponent.cs b/Content.Client/GameObjects/Components/IconSmoothing/IconSmoothComponent.cs
index b3bf131c3e..18db2fb3e2 100644
--- a/Content.Client/GameObjects/Components/IconSmoothing/IconSmoothComponent.cs
+++ b/Content.Client/GameObjects/Components/IconSmoothing/IconSmoothComponent.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using Content.Shared.GameObjects;
using SS14.Client.Interfaces.GameObjects.Components;
using SS14.Shared.GameObjects;
using SS14.Shared.GameObjects.Components.Transform;
diff --git a/Content.Client/GameObjects/Components/Items/ClientHandsComponent.cs b/Content.Client/GameObjects/Components/Items/ClientHandsComponent.cs
index 55c8bae380..2411aefa22 100644
--- a/Content.Client/GameObjects/Components/Items/ClientHandsComponent.cs
+++ b/Content.Client/GameObjects/Components/Items/ClientHandsComponent.cs
@@ -174,6 +174,11 @@ namespace Content.Client.GameObjects
SendNetworkMessage(new ClientChangedHandMsg(index));
}
+ public void AttackByInHand(string index)
+ {
+ SendNetworkMessage(new ClientAttackByInHandMsg(index));
+ }
+
public void UseActiveHand()
{
if (GetEntity(ActiveIndex) != null)
diff --git a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs
new file mode 100644
index 0000000000..ae262d08c8
--- /dev/null
+++ b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs
@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Content.Shared.GameObjects;
+using Content.Shared.GameObjects.EntitySystemMessages;
+using Content.Shared.Input;
+using SS14.Client.GameObjects.EntitySystems;
+using SS14.Client.Interfaces.Input;
+using SS14.Client.Interfaces.State;
+using SS14.Client.Interfaces.UserInterface;
+using SS14.Client.Player;
+using SS14.Client.State.States;
+using SS14.Client.UserInterface.Controls;
+using SS14.Shared.GameObjects;
+using SS14.Shared.GameObjects.Systems;
+using SS14.Shared.Input;
+using SS14.Shared.Interfaces.GameObjects;
+using SS14.Shared.Interfaces.Network;
+using SS14.Shared.IoC;
+using SS14.Shared.Log;
+using SS14.Shared.Map;
+using SS14.Shared.Maths;
+using SS14.Shared.Utility;
+
+namespace Content.Client.GameObjects.EntitySystems
+{
+ public class VerbSystem : EntitySystem
+ {
+#pragma warning disable 649
+ [Dependency] private readonly IStateManager _stateManager;
+ [Dependency] private readonly IEntityManager _entityManager;
+ [Dependency] private readonly IPlayerManager _playerManager;
+ [Dependency] private readonly IInputManager _inputManager;
+ [Dependency] private readonly IUserInterfaceManager _userInterfaceManager;
+#pragma warning restore 649
+
+ private Popup _currentPopup;
+ private EntityUid _currentEntity;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ IoCManager.InjectDependencies(this);
+
+ var input = EntitySystemManager.GetEntitySystem();
+ input.BindMap.BindFunction(ContentKeyFunctions.OpenContextMenu,
+ new PointerInputCmdHandler(OnOpenContextMenu));
+ }
+
+ public override void RegisterMessageTypes()
+ {
+ base.RegisterMessageTypes();
+
+ RegisterMessageType();
+ }
+
+ public void OpenContextMenu(IEntity entity, ScreenCoordinates screenCoordinates)
+ {
+ if (_currentPopup != null)
+ {
+ _closeContextMenu();
+ }
+
+ _currentEntity = entity.Uid;
+ _currentPopup = new Popup();
+ _currentPopup.UserInterfaceManager.StateRoot.AddChild(_currentPopup);
+ _currentPopup.OnPopupHide += _closeContextMenu;
+ var vBox = new VBoxContainer("ButtonBox");
+ _currentPopup.AddChild(vBox);
+
+ vBox.AddChild(new Label {Text = "Waiting on Server..."});
+ RaiseNetworkEvent(new VerbSystemMessages.RequestVerbsMessage(_currentEntity));
+
+ var size = vBox.CombinedMinimumSize;
+ var box = UIBox2.FromDimensions(screenCoordinates.AsVector, size);
+ _currentPopup.Open(box);
+ }
+
+ private void OnOpenContextMenu(in PointerInputCmdHandler.PointerInputCmdArgs args)
+ {
+ if (_currentPopup != null)
+ {
+ _closeContextMenu();
+ return;
+ }
+
+ if (!(_stateManager.CurrentState is GameScreen gameScreen))
+ {
+ return;
+ }
+
+ var entities = gameScreen.GetEntitiesUnderPosition(args.Coordinates);
+
+ _currentPopup = new Popup();
+ _currentPopup.OnPopupHide += _closeContextMenu;
+ var vBox = new VBoxContainer("ButtonBox");
+ _currentPopup.AddChild(vBox);
+ foreach (var entity in entities)
+ {
+ var button = new Button {Text = entity.Name};
+ vBox.AddChild(button);
+ button.OnPressed += _ => OnContextButtonPressed(entity);
+ }
+
+ _currentPopup.UserInterfaceManager.StateRoot.AddChild(_currentPopup);
+
+ var size = vBox.CombinedMinimumSize;
+ var box = UIBox2.FromDimensions(args.ScreenCoordinates.Position, size);
+ _currentPopup.Open(box);
+ }
+
+ private void OnContextButtonPressed(IEntity entity)
+ {
+ OpenContextMenu(entity, new ScreenCoordinates(_inputManager.MouseScreenPosition));
+ }
+
+ public override void HandleNetMessage(INetChannel channel, EntitySystemMessage message)
+ {
+ base.HandleNetMessage(channel, message);
+
+ switch (message)
+ {
+ case VerbSystemMessages.VerbsResponseMessage resp:
+ _fillEntityPopup(resp);
+ break;
+ }
+ }
+
+ private void _fillEntityPopup(VerbSystemMessages.VerbsResponseMessage msg)
+ {
+ if (_currentEntity != msg.Entity || !_entityManager.TryGetEntity(_currentEntity, out var entity))
+ {
+ return;
+ }
+
+ DebugTools.AssertNotNull(_currentPopup);
+
+ var buttons = new List