Right click verbs work.
This commit is contained in:
Pieter-Jan Briers
2018-11-21 20:58:11 +01:00
committed by GitHub
parent 8038ebe37d
commit b0f212bad5
20 changed files with 982 additions and 112 deletions

View File

@@ -74,6 +74,7 @@
<Compile Include="GameObjects\Components\Power\ApcBoundUserInterface.cs" /> <Compile Include="GameObjects\Components\Power\ApcBoundUserInterface.cs" />
<Compile Include="GameObjects\Components\Power\PowerCellVisualizer2D.cs" /> <Compile Include="GameObjects\Components\Power\PowerCellVisualizer2D.cs" />
<Compile Include="GameObjects\Components\Storage\ClientStorageComponent.cs" /> <Compile Include="GameObjects\Components\Storage\ClientStorageComponent.cs" />
<Compile Include="GameObjects\EntitySystems\VerbSystem.cs" />
<Compile Include="Input\ContentContexts.cs" /> <Compile Include="Input\ContentContexts.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="GameObjects\Components\Items\ClientHandsComponent.cs" /> <Compile Include="GameObjects\Components\Items\ClientHandsComponent.cs" />

View File

@@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using Content.Shared.GameObjects;
using SS14.Client.Interfaces.GameObjects.Components; using SS14.Client.Interfaces.GameObjects.Components;
using SS14.Shared.GameObjects; using SS14.Shared.GameObjects;
using SS14.Shared.GameObjects.Components.Transform; using SS14.Shared.GameObjects.Components.Transform;

View File

@@ -174,6 +174,11 @@ namespace Content.Client.GameObjects
SendNetworkMessage(new ClientChangedHandMsg(index)); SendNetworkMessage(new ClientChangedHandMsg(index));
} }
public void AttackByInHand(string index)
{
SendNetworkMessage(new ClientAttackByInHandMsg(index));
}
public void UseActiveHand() public void UseActiveHand()
{ {
if (GetEntity(ActiveIndex) != null) if (GetEntity(ActiveIndex) != null)

View File

@@ -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<InputSystem>();
input.BindMap.BindFunction(ContentKeyFunctions.OpenContextMenu,
new PointerInputCmdHandler(OnOpenContextMenu));
}
public override void RegisterMessageTypes()
{
base.RegisterMessageTypes();
RegisterMessageType<VerbSystemMessages.VerbsResponseMessage>();
}
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<Button>();
var vBox = _currentPopup.GetChild<VBoxContainer>("ButtonBox");
vBox.DisposeAllChildren();
foreach (var data in msg.Verbs)
{
var button = new Button {Text = data.Text, Disabled = !data.Available};
if (data.Available)
{
button.OnPressed += _ =>
{
RaiseNetworkEvent(new VerbSystemMessages.UseVerbMessage(_currentEntity, data.Key));
_closeContextMenu();
};
}
buttons.Add(button);
}
var user = GetUserEntity();
foreach (var (component, verb) in VerbUtility.GetVerbs(entity))
{
if (verb.RequireInteractionRange)
{
var distanceSquared = (user.Transform.WorldPosition - entity.Transform.WorldPosition)
.LengthSquared;
if (distanceSquared > Verb.InteractionRangeSquared)
{
continue;
}
}
var disabled = verb.IsDisabled(user, component);
var button = new Button
{
Text = verb.GetText(user, component),
Disabled = disabled
};
if (!disabled)
{
button.OnPressed += _ =>
{
_closeContextMenu();
try
{
verb.Activate(user, component);
}
catch (Exception e)
{
Logger.ErrorS("verb", "Exception in verb {0} on {1}:\n{2}", verb, entity, e);
}
};
}
buttons.Add(button);
}
if (buttons.Count > 0)
{
buttons.Sort((a, b) => string.Compare(a.Text, b.Text, StringComparison.Ordinal));
foreach (var button in buttons)
{
vBox.AddChild(button);
}
}
else
{
var panel = new PanelContainer();
panel.AddChild(new Label {Text = "No verbs!"});
vBox.AddChild(panel);
}
_currentPopup.Size = vBox.CombinedMinimumSize;
// If we're at the bottom of the window and the menu would go below the bottom of the window,
// shift it up so it extends UP.
var bottomCoord = vBox.CombinedMinimumSize.Y + _currentPopup.Position.Y;
if (bottomCoord > _userInterfaceManager.StateRoot.Size.Y)
{
_currentPopup.Position = _currentPopup.Position - new Vector2(0, vBox.CombinedMinimumSize.Y);
}
}
private void _closeContextMenu()
{
_currentPopup?.Dispose();
_currentPopup = null;
_currentEntity = EntityUid.Invalid;
}
private IEntity GetUserEntity()
{
return _playerManager.LocalPlayer.ControlledEntity;
}
}
}

View File

@@ -20,6 +20,7 @@ namespace Content.Client.Input
human.AddFunction(ContentKeyFunctions.UseItemInHand); human.AddFunction(ContentKeyFunctions.UseItemInHand);
human.AddFunction(ContentKeyFunctions.ActivateItemInWorld); human.AddFunction(ContentKeyFunctions.ActivateItemInWorld);
human.AddFunction(ContentKeyFunctions.ThrowItemInHand); human.AddFunction(ContentKeyFunctions.ThrowItemInHand);
human.AddFunction(ContentKeyFunctions.OpenContextMenu);
var ghost = contexts.New("ghost", "common"); var ghost = contexts.New("ghost", "common");
ghost.AddFunction(EngineKeyFunctions.MoveUp); ghost.AddFunction(EngineKeyFunctions.MoveUp);
@@ -27,6 +28,7 @@ namespace Content.Client.Input
ghost.AddFunction(EngineKeyFunctions.MoveLeft); ghost.AddFunction(EngineKeyFunctions.MoveLeft);
ghost.AddFunction(EngineKeyFunctions.MoveRight); ghost.AddFunction(EngineKeyFunctions.MoveRight);
ghost.AddFunction(EngineKeyFunctions.Run); ghost.AddFunction(EngineKeyFunctions.Run);
ghost.AddFunction(ContentKeyFunctions.OpenContextMenu);
} }
} }
} }

View File

@@ -10,5 +10,6 @@ namespace Content.Client.Interfaces.GameObjects
string ActiveIndex { get; } string ActiveIndex { get; }
void SendChangeHand(string index); void SendChangeHand(string index);
void AttackByInHand(string index);
} }
} }

View File

@@ -1,4 +1,5 @@
using Content.Client.GameObjects; using Content.Client.GameObjects;
using Content.Client.GameObjects.EntitySystems;
using Content.Client.Interfaces.GameObjects; using Content.Client.Interfaces.GameObjects;
using SS14.Client.GameObjects; using SS14.Client.GameObjects;
using SS14.Client.Graphics; using SS14.Client.Graphics;
@@ -14,6 +15,7 @@ using SS14.Client.UserInterface.Controls;
using SS14.Shared.Interfaces.GameObjects; using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.IoC; using SS14.Shared.IoC;
using SS14.Shared.Log; using SS14.Shared.Log;
using SS14.Shared.Map;
using SS14.Shared.Maths; using SS14.Shared.Maths;
namespace Content.Client.UserInterface namespace Content.Client.UserInterface
@@ -21,7 +23,9 @@ namespace Content.Client.UserInterface
public class HandsGui : Control public class HandsGui : Control
{ {
private static readonly Color _inactiveColor = new Color(90, 90, 90); private static readonly Color _inactiveColor = new Color(90, 90, 90);
private const int BOX_SPACING = 1; private const int BOX_SPACING = 1;
// The boxes are square so that's both width and height. // The boxes are square so that's both width and height.
private const int BOX_SIZE = 50; private const int BOX_SIZE = 50;
@@ -123,6 +127,7 @@ namespace Content.Client.UserInterface
{ {
return; return;
} }
UpdateDraw(); UpdateDraw();
if (!TryGetHands(out IHandsComponent hands)) if (!TryGetHands(out IHandsComponent hands))
@@ -142,8 +147,8 @@ namespace Content.Client.UserInterface
LeftHand.MirrorHandle?.Dispose(); LeftHand.MirrorHandle?.Dispose();
LeftHand.MirrorHandle = GetSpriteMirror(left); LeftHand.MirrorHandle = GetSpriteMirror(left);
LeftHand.MirrorHandle.AttachToControl(this); LeftHand.MirrorHandle.AttachToControl(this);
LeftHand.MirrorHandle.Offset = new Vector2(handL.Left + (int)(handL.Width / 2f), LeftHand.MirrorHandle.Offset = new Vector2(handL.Left + (int) (handL.Width / 2f),
handL.Top + (int)(handL.Height / 2f)); handL.Top + (int) (handL.Height / 2f));
} }
} }
else else
@@ -161,8 +166,8 @@ namespace Content.Client.UserInterface
RightHand.MirrorHandle?.Dispose(); RightHand.MirrorHandle?.Dispose();
RightHand.MirrorHandle = GetSpriteMirror(right); RightHand.MirrorHandle = GetSpriteMirror(right);
RightHand.MirrorHandle.AttachToControl(this); RightHand.MirrorHandle.AttachToControl(this);
RightHand.MirrorHandle.Offset = new Vector2(handR.Left + (int)(handR.Width / 2f), RightHand.MirrorHandle.Offset = new Vector2(handR.Left + (int) (handR.Width / 2f),
handR.Top + (int)(handR.Height / 2f)); handR.Top + (int) (handR.Height / 2f));
} }
} }
else else
@@ -187,40 +192,79 @@ namespace Content.Client.UserInterface
return; return;
//Todo: remove hands interface, so weird //Todo: remove hands interface, so weird
((HandsComponent)hands).UseActiveHand(); ((HandsComponent) hands).UseActiveHand();
}
private void AttackByInHand(string hand)
{
if (!TryGetHands(out var hands))
return;
hands.AttackByInHand(hand);
} }
protected override bool HasPoint(Vector2 point) protected override bool HasPoint(Vector2 point)
{ {
return handL.Contains((Vector2i)point) || handR.Contains((Vector2i)point); return handL.Contains((Vector2i) point) || handR.Contains((Vector2i) point);
} }
protected override void MouseDown(GUIMouseButtonEventArgs args) protected override void MouseDown(GUIMouseButtonEventArgs args)
{ {
base.MouseDown(args); base.MouseDown(args);
var lefthandcontains = handL.Contains((Vector2i)args.RelativePosition); var leftHandContains = handL.Contains((Vector2i) args.RelativePosition);
var righthandcontains = handR.Contains((Vector2i)args.RelativePosition); var rightHandContains = handR.Contains((Vector2i) args.RelativePosition);
string handIndex;
if (leftHandContains)
{
handIndex = "left";
}
else if (rightHandContains)
{
handIndex = "right";
}
else
{
return;
}
if (args.Button == Mouse.Button.Left) if (args.Button == Mouse.Button.Left)
{ {
if (!TryGetHands(out IHandsComponent hands)) if (!TryGetHands(out var hands))
return; return;
if ((hands.ActiveIndex == "left" && lefthandcontains)
|| (hands.ActiveIndex == "right" && righthandcontains)) if (hands.ActiveIndex == handIndex)
{
UseActiveHand(); UseActiveHand();
}
else
{
AttackByInHand(handIndex);
}
} }
else if (args.Button == Mouse.Button.Middle)
{
SendSwitchHandTo(handIndex);
}
else if (args.Button == Mouse.Button.Right) else if (args.Button == Mouse.Button.Right)
{ {
if (lefthandcontains) if (!TryGetHands(out var hands))
{ {
SendSwitchHandTo("left"); return;
} }
if (righthandcontains)
var entity = hands.GetEntity(handIndex);
if (entity == null)
{ {
SendSwitchHandTo("right"); return;
} }
var esm = IoCManager.Resolve<IEntitySystemManager>();
esm.GetEntitySystem<VerbSystem>().OpenContextMenu(entity, new ScreenCoordinates(args.GlobalPosition));
} }
} }
@@ -230,13 +274,14 @@ namespace Content.Client.UserInterface
{ {
return component.CreateProxy(); return component.CreateProxy();
} }
return null; return null;
} }
private struct UiHandInfo private struct UiHandInfo
{ {
public IEntity Entity { get; set; } public IEntity Entity { get; set; }
public ISpriteProxy MirrorHandle { get; set; } public ISpriteProxy MirrorHandle { get; set; }
} }
} }
} }

View File

@@ -104,6 +104,7 @@
<Compile Include="GameObjects\EntitySystems\PowerApcSystem.cs" /> <Compile Include="GameObjects\EntitySystems\PowerApcSystem.cs" />
<Compile Include="GameObjects\EntitySystems\PowerSmesSystem.cs" /> <Compile Include="GameObjects\EntitySystems\PowerSmesSystem.cs" />
<Compile Include="GameObjects\EntitySystems\PowerSystem.cs" /> <Compile Include="GameObjects\EntitySystems\PowerSystem.cs" />
<Compile Include="GameObjects\EntitySystems\VerbSystem.cs" />
<Compile Include="GameObjects\EntitySystems\StorageSystem.cs" /> <Compile Include="GameObjects\EntitySystems\StorageSystem.cs" />
<Compile Include="GameObjects\EntitySystems\WelderSystem.cs" /> <Compile Include="GameObjects\EntitySystems\WelderSystem.cs" />
<Compile Include="GameObjects\EntitySystems\TemperatureSystem.cs" /> <Compile Include="GameObjects\EntitySystems\TemperatureSystem.cs" />
@@ -168,4 +169,4 @@
<Compile Include="GameObjects\Components\Construction\ConstructorComponent.cs" /> <Compile Include="GameObjects\Components\Construction\ConstructorComponent.cs" />
<Compile Include="GameObjects\Components\Construction\ConstructionComponent.cs" /> <Compile Include="GameObjects\Components\Construction\ConstructionComponent.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -4,6 +4,7 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects; using Content.Server.Interfaces.GameObjects;
using Content.Shared.GameObjects; using Content.Shared.GameObjects;
using Content.Shared.Input; using Content.Shared.Input;
using JetBrains.Annotations;
using SS14.Server.GameObjects; using SS14.Server.GameObjects;
using SS14.Server.GameObjects.Components.Container; using SS14.Server.GameObjects.Components.Container;
using SS14.Server.Interfaces.Player; using SS14.Server.Interfaces.Player;
@@ -13,8 +14,10 @@ using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.Interfaces.GameObjects.Components; using SS14.Shared.Interfaces.GameObjects.Components;
using SS14.Shared.Interfaces.Network; using SS14.Shared.Interfaces.Network;
using SS14.Shared.IoC; using SS14.Shared.IoC;
using SS14.Shared.Log;
using SS14.Shared.Map; using SS14.Shared.Map;
using SS14.Shared.Serialization; using SS14.Shared.Serialization;
using SS14.Shared.Utility;
using SS14.Shared.ViewVariables; using SS14.Shared.ViewVariables;
namespace Content.Server.GameObjects namespace Content.Server.GameObjects
@@ -39,8 +42,8 @@ namespace Content.Server.GameObjects
} }
} }
private Dictionary<string, ContainerSlot> hands = new Dictionary<string, ContainerSlot>(); [ViewVariables] private Dictionary<string, ContainerSlot> hands = new Dictionary<string, ContainerSlot>();
private List<string> orderedHands = new List<string>(); [ViewVariables] private List<string> orderedHands = new List<string>();
// Mostly arbitrary. // Mostly arbitrary.
public const float PICKUP_RANGE = 2; public const float PICKUP_RANGE = 2;
@@ -74,7 +77,7 @@ namespace Content.Server.GameObjects
/// <inheritdoc /> /// <inheritdoc />
public void RemoveHandEntity(IEntity entity) public void RemoveHandEntity(IEntity entity)
{ {
if(entity == null) if (entity == null)
return; return;
foreach (var slot in hands.Values) foreach (var slot in hands.Values)
@@ -155,13 +158,20 @@ namespace Content.Server.GameObjects
return slot.CanInsert(item.Owner); return slot.CanInsert(item.Owner);
} }
/// <summary> public string FindHand(IEntity entity)
/// Drops the item in a slot. {
/// </summary> foreach (var (index, slot) in hands)
/// <param name="slot">The slot to drop the item from.</param> {
/// <param name="coords"></param> if (slot.ContainedEntity == entity)
/// <returns>True if an item was dropped, false otherwise.</returns> {
public bool Drop(string slot, GridLocalCoordinates? coords) return index;
}
}
return null;
}
public bool Drop(string slot, GridLocalCoordinates coords)
{ {
if (!CanDrop(slot)) if (!CanDrop(slot))
{ {
@@ -178,14 +188,119 @@ namespace Content.Server.GameObjects
item.RemovedFromSlot(); item.RemovedFromSlot();
// TODO: The item should be dropped to the container our owner is in, if any. // TODO: The item should be dropped to the container our owner is in, if any.
var itemTransform = item.Owner.GetComponent<ITransformComponent>(); item.Owner.Transform.LocalPosition = coords;
itemTransform.LocalPosition = coords ?? Owner.GetComponent<ITransformComponent>().LocalPosition;
Dirty(); Dirty();
return true; return true;
} }
public bool Drop(IEntity entity, GridLocalCoordinates coords)
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
var slot = FindHand(entity);
if (slot == null)
{
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
}
return Drop(slot, coords);
}
public bool Drop(string slot)
{
if (!CanDrop(slot))
{
return false;
}
var inventorySlot = hands[slot];
var item = inventorySlot.ContainedEntity.GetComponent<ItemComponent>();
if (!inventorySlot.Remove(inventorySlot.ContainedEntity))
{
return false;
}
item.RemovedFromSlot();
// TODO: The item should be dropped to the container our owner is in, if any.
item.Owner.Transform.LocalPosition = Owner.Transform.LocalPosition;
Dirty();
return true;
}
public bool Drop(IEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
var slot = FindHand(entity);
if (slot == null)
{
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
}
return Drop(slot);
}
public bool Drop(string slot, BaseContainer targetContainer)
{
if (!CanDrop(slot))
{
return false;
}
var inventorySlot = hands[slot];
var item = inventorySlot.ContainedEntity.GetComponent<ItemComponent>();
if (!inventorySlot.CanRemove(inventorySlot.ContainedEntity))
{
return false;
}
if (!targetContainer.CanInsert(inventorySlot.ContainedEntity))
{
return false;
}
if (!inventorySlot.Remove(inventorySlot.ContainedEntity))
{
throw new InvalidOperationException();
}
item.RemovedFromSlot();
if (!targetContainer.Insert(item.Owner))
{
throw new InvalidOperationException();
}
Dirty();
return true;
}
public bool Drop(IEntity entity, BaseContainer targetContainer)
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
var slot = FindHand(entity);
if (slot == null)
{
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
}
return Drop(slot, targetContainer);
}
/// <summary> /// <summary>
/// Checks whether an item can be dropped from the specified slot. /// Checks whether an item can be dropped from the specified slot.
/// </summary> /// </summary>
@@ -212,10 +327,12 @@ namespace Content.Server.GameObjects
{ {
orderedHands.Add(index); orderedHands.Add(index);
} }
if (ActiveIndex == null) if (ActiveIndex == null)
{ {
ActiveIndex = index; ActiveIndex = index;
} }
Dirty(); Dirty();
} }
@@ -241,6 +358,7 @@ namespace Content.Server.GameObjects
activeIndex = orderedHands[0]; activeIndex = orderedHands[0];
} }
} }
Dirty(); Dirty();
} }
@@ -264,6 +382,7 @@ namespace Content.Server.GameObjects
dict[hand.Key] = hand.Value.ContainedEntity.Uid; dict[hand.Key] = hand.Value.ContainedEntity.Uid;
} }
} }
return new HandsComponentState(dict, ActiveIndex); return new HandsComponentState(dict, ActiveIndex);
} }
@@ -288,36 +407,61 @@ namespace Content.Server.GameObjects
} }
} }
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null) public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
IComponent component = null)
{ {
base.HandleMessage(message, netChannel, component); base.HandleMessage(message, netChannel, component);
switch (message) switch (message)
{ {
case ClientChangedHandMsg msg: case ClientChangedHandMsg msg:
{ {
var playerMan = IoCManager.Resolve<IPlayerManager>(); var playerMan = IoCManager.Resolve<IPlayerManager>();
var session = playerMan.GetSessionByChannel(netChannel); var session = playerMan.GetSessionByChannel(netChannel);
var playerentity = session.AttachedEntity; var playerEntity = session.AttachedEntity;
if (playerentity == Owner && HasHand(msg.Index)) if (playerEntity == Owner && HasHand(msg.Index))
ActiveIndex = msg.Index; ActiveIndex = msg.Index;
break; break;
}
case ClientAttackByInHandMsg msg:
{
if (!hands.TryGetValue(msg.Index, out var slot))
{
Logger.WarningS("go.comp.hands", "Got a ClientAttackByInHandMsg with invalid hand index '{0}'",
msg.Index);
return;
} }
var playerMan = IoCManager.Resolve<IPlayerManager>();
var session = playerMan.GetSessionByChannel(netChannel);
var playerEntity = session.AttachedEntity;
var used = GetActiveHand?.Owner;
if (playerEntity == Owner && used != null)
{
InteractionSystem.Interaction(Owner, used, slot.ContainedEntity,
GridLocalCoordinates.Nullspace);
}
break;
}
case ActivateInhandMsg msg: case ActivateInhandMsg msg:
{ {
var playerMan = IoCManager.Resolve<IPlayerManager>(); var playerMan = IoCManager.Resolve<IPlayerManager>();
var session = playerMan.GetSessionByChannel(netChannel); var session = playerMan.GetSessionByChannel(netChannel);
var playerentity = session.AttachedEntity; var playerEntity = session.AttachedEntity;
var used = GetActiveHand?.Owner; var used = GetActiveHand?.Owner;
if (playerentity == Owner && used != null) if (playerEntity == Owner && used != null)
{ {
InteractionSystem.TryUseInteraction(Owner, used); InteractionSystem.TryUseInteraction(Owner, used);
}
break;
} }
break;
}
} }
} }
} }

View File

@@ -1,31 +1,32 @@
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.Components.Power; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects;
using Content.Shared.GameObjects;
using SS14.Server.GameObjects; using SS14.Server.GameObjects;
using SS14.Server.GameObjects.Components.Container; using SS14.Server.GameObjects.Components.Container;
using SS14.Shared.Enums; using SS14.Shared.Enums;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.GameObjects; using SS14.Shared.GameObjects;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.ViewVariables; using SS14.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Interactable namespace Content.Server.GameObjects.Components.Interactable
{ {
/// <summary> /// <summary>
/// Component that represents a handheld lightsource which can be toggled on and off. /// Component that represents a handheld lightsource which can be toggled on and off.
/// </summary> /// </summary>
class HandheldLightComponent : Component, IUse, IExamine internal class HandheldLightComponent : Component, IUse, IExamine, IAttackby
{ {
public const float Wattage = 10;
[ViewVariables] private ContainerSlot _cellContainer;
private PointLightComponent _pointLight; private PointLightComponent _pointLight;
private SpriteComponent _spriteComponent; private SpriteComponent _spriteComponent;
[ViewVariables] private ContainerSlot _cellContainer;
[ViewVariables]
private PowerCellComponent Cell private PowerCellComponent Cell
{ {
get get
{ {
if (_cellContainer.ContainedEntity == null) if (_cellContainer.ContainedEntity == null) return null;
{
return null;
}
_cellContainer.ContainedEntity.TryGetComponent(out PowerCellComponent cell); _cellContainer.ContainedEntity.TryGetComponent(out PowerCellComponent cell);
return cell; return cell;
@@ -35,12 +36,33 @@ namespace Content.Server.GameObjects.Components.Interactable
public override string Name => "HandheldLight"; public override string Name => "HandheldLight";
/// <summary> /// <summary>
/// Status of light, whether or not it is emitting light. /// Status of light, whether or not it is emitting light.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public bool Activated { get; private set; } = false; public bool Activated { get; private set; }
public const float Wattage = 10; bool IAttackby.Attackby(IEntity user, IEntity attackwith)
{
if (!attackwith.HasComponent<PowerCellComponent>()) return false;
if (Cell != null) return false;
user.GetComponent<IHandsComponent>().Drop(attackwith, _cellContainer);
return _cellContainer.Insert(attackwith);
}
string IExamine.Examine()
{
if (Activated) return "The light is currently on.";
return null;
}
bool IUse.UseEntity(IEntity user)
{
return ToggleStatus();
}
public override void Initialize() public override void Initialize()
{ {
@@ -58,13 +80,8 @@ namespace Content.Server.GameObjects.Components.Interactable
} }
} }
bool IUse.UseEntity(IEntity user)
{
return ToggleStatus();
}
/// <summary> /// <summary>
/// Illuminates the light if it is not active, extinguishes it if it is active. /// Illuminates the light if it is not active, extinguishes it if it is active.
/// </summary> /// </summary>
/// <returns>True if the light's status was toggled, false otherwise.</returns> /// <returns>True if the light's status was toggled, false otherwise.</returns>
public bool ToggleStatus() public bool ToggleStatus()
@@ -90,10 +107,7 @@ namespace Content.Server.GameObjects.Components.Interactable
public void TurnOff() public void TurnOff()
{ {
if (!Activated) if (!Activated) return;
{
return;
}
_spriteComponent.LayerSetState(0, "lantern_off"); _spriteComponent.LayerSetState(0, "lantern_off");
_pointLight.State = LightState.Off; _pointLight.State = LightState.Off;
@@ -102,50 +116,57 @@ namespace Content.Server.GameObjects.Components.Interactable
public void TurnOn() public void TurnOn()
{ {
if (Activated) if (Activated) return;
{
return;
}
var cell = Cell; var cell = Cell;
if (cell == null) if (cell == null) return;
{
return;
}
// To prevent having to worry about frame time in here. // To prevent having to worry about frame time in here.
// Let's just say you need a whole second of charge before you can turn it on. // Let's just say you need a whole second of charge before you can turn it on.
// Simple enough. // Simple enough.
if (cell.AvailableCharge(1) < Wattage) if (cell.AvailableCharge(1) < Wattage) return;
{
return;
}
_spriteComponent.LayerSetState(0, "lantern_on"); _spriteComponent.LayerSetState(0, "lantern_on");
_pointLight.State = LightState.On; _pointLight.State = LightState.On;
} }
string IExamine.Examine()
{
if (Activated)
{
return "The light is currently on.";
}
return null;
}
public void OnUpdate(float frameTime) public void OnUpdate(float frameTime)
{ {
if (!Activated) if (!Activated) return;
{
return;
}
var cell = Cell; var cell = Cell;
if (cell == null || !cell.TryDeductWattage(Wattage, frameTime)) if (cell == null || !cell.TryDeductWattage(Wattage, frameTime)) TurnOff();
}
private void EjectCell(IEntity user)
{
if (Cell == null) return;
var cell = Cell;
if (!_cellContainer.Remove(cell.Owner)) return;
if (!user.TryGetComponent(out HandsComponent hands)
|| !hands.PutInHand(cell.Owner.GetComponent<ItemComponent>()))
cell.Owner.Transform.LocalPosition = user.Transform.LocalPosition;
}
[Verb]
public sealed class EjectCellVerb : Verb<HandheldLightComponent>
{
protected override string GetText(IEntity user, HandheldLightComponent component)
{ {
TurnOff(); return component.Cell == null ? "Eject cell (cell missing)" : "Eject cell";
}
protected override bool IsDisabled(IEntity user, HandheldLightComponent component)
{
return component.Cell == null;
}
protected override void Activate(IEntity user, HandheldLightComponent component)
{
component.EjectCell(user);
} }
} }
} }

View File

@@ -35,7 +35,7 @@ namespace Content.Server.GameObjects.EntitySystems
input.BindMap.BindFunction(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem)); input.BindMap.BindFunction(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem));
input.BindMap.BindFunction(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)); input.BindMap.BindFunction(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem));
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Shutdown() public override void Shutdown()
{ {
@@ -118,13 +118,14 @@ namespace Content.Server.GameObjects.EntitySystems
var transform = ent.Transform; var transform = ent.Transform;
GridLocalCoordinates? dropPos = null;
if (transform.LocalPosition.InRange(coords, InteractionSystem.INTERACTION_RANGE)) if (transform.LocalPosition.InRange(coords, InteractionSystem.INTERACTION_RANGE))
{ {
dropPos = coords; handsComp.Drop(handsComp.ActiveIndex, coords);
}
else
{
handsComp.Drop(handsComp.ActiveIndex);
} }
handsComp.Drop(handsComp.ActiveIndex, dropPos);
} }
private static void HandleActivateItem(ICommonSession session) private static void HandleActivateItem(ICommonSession session)
@@ -160,7 +161,7 @@ namespace Content.Server.GameObjects.EntitySystems
stackComp.Use(1); stackComp.Use(1);
throwEnt = throwEnt.EntityManager.ForceSpawnEntityAt(throwEnt.Prototype.ID, plyEnt.Transform.LocalPosition); throwEnt = throwEnt.EntityManager.ForceSpawnEntityAt(throwEnt.Prototype.ID, plyEnt.Transform.LocalPosition);
} }
if (!throwEnt.TryGetComponent(out CollidableComponent colComp)) if (!throwEnt.TryGetComponent(out CollidableComponent colComp))
{ {
colComp = throwEnt.AddComponent<CollidableComponent>(); colComp = throwEnt.AddComponent<CollidableComponent>();
@@ -180,7 +181,7 @@ namespace Content.Server.GameObjects.EntitySystems
{ {
projComp = throwEnt.AddComponent<ThrownItemComponent>(); projComp = throwEnt.AddComponent<ThrownItemComponent>();
} }
projComp.IgnoreEntity(plyEnt); projComp.IgnoreEntity(plyEnt);
var transform = plyEnt.Transform; var transform = plyEnt.Transform;

View File

@@ -0,0 +1,113 @@
using System.Collections.Generic;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.EntitySystemMessages;
using SS14.Server.Interfaces.Player;
using SS14.Shared.GameObjects;
using SS14.Shared.GameObjects.Systems;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.IoC;
using static Content.Shared.GameObjects.EntitySystemMessages.VerbSystemMessages;
namespace Content.Server.GameObjects.EntitySystems
{
public class VerbSystem : EntitySystem
{
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager;
[Dependency] private readonly IPlayerManager _playerManager;
#pragma warning restore 649
public override void Initialize()
{
base.Initialize();
IoCManager.InjectDependencies(this);
}
public override void RegisterMessageTypes()
{
base.RegisterMessageTypes();
RegisterMessageType<RequestVerbsMessage>();
RegisterMessageType<UseVerbMessage>();
}
public override void HandleNetMessage(INetChannel channel, EntitySystemMessage message)
{
base.HandleNetMessage(channel, message);
switch (message)
{
case RequestVerbsMessage req:
{
if (!_entityManager.TryGetEntity(req.EntityUid, out var entity))
{
return;
}
var session = _playerManager.GetSessionByChannel(channel);
var userEntity = session.AttachedEntity;
var data = new List<VerbsResponseMessage.VerbData>();
foreach (var (component, verb) in VerbUtility.GetVerbs(entity))
{
if (verb.RequireInteractionRange)
{
var distanceSquared = (userEntity.Transform.WorldPosition - entity.Transform.WorldPosition)
.LengthSquared;
if (distanceSquared > Verb.InteractionRangeSquared)
{
continue;
}
}
// TODO: These keys being giant strings is inefficient as hell.
data.Add(new VerbsResponseMessage.VerbData(verb.GetText(userEntity, component),
$"{component.GetType()}:{verb.GetType()}",
!verb.IsDisabled(userEntity, component)));
}
var response = new VerbsResponseMessage(data, req.EntityUid);
RaiseNetworkEvent(response, channel);
break;
}
case UseVerbMessage use:
{
if (!_entityManager.TryGetEntity(use.EntityUid, out var entity))
{
return;
}
var session = _playerManager.GetSessionByChannel(channel);
var userEntity = session.AttachedEntity;
foreach (var (component, verb) in VerbUtility.GetVerbs(entity))
{
if ($"{component.GetType()}:{verb.GetType()}" != use.VerbKey)
{
continue;
}
if (verb.RequireInteractionRange)
{
var distanceSquared = (userEntity.Transform.WorldPosition - entity.Transform.WorldPosition)
.LengthSquared;
if (distanceSquared > Verb.InteractionRangeSquared)
{
break;
}
}
verb.Activate(userEntity, component);
break;
}
break;
}
}
}
}
}

View File

@@ -1,6 +1,8 @@
using Content.Server.GameObjects; using System;
using Content.Server.GameObjects;
using SS14.Shared.Interfaces.GameObjects; using SS14.Shared.Interfaces.GameObjects;
using System.Collections.Generic; using System.Collections.Generic;
using SS14.Server.GameObjects.Components.Container;
using SS14.Shared.Map; using SS14.Shared.Map;
namespace Content.Server.Interfaces.GameObjects namespace Content.Server.Interfaces.GameObjects
@@ -69,12 +71,96 @@ namespace Content.Server.Interfaces.GameObjects
bool CanPutInHand(ItemComponent item, string index); bool CanPutInHand(ItemComponent item, string index);
/// <summary> /// <summary>
/// Drops an item on the ground, removing it from the hand. /// Finds the hand slot holding the specified entity, if any.
/// </summary> /// </summary>
/// <param name="index">The hand to drop from.</param> /// <param name="entity">
/// The entity to look for in our hands.
/// </param>
/// <returns>
/// The index of the hand slot if the entity is indeed held, <see langword="null" /> otherwise.
/// </returns>
string FindHand(IEntity entity);
/// <summary>
/// Drops the item contained in the slot to the same position as our entity.
/// </summary>
/// <param name="slot">The slot of which to drop to drop the item.</param>
/// <returns>True on success, false if something blocked the drop.</returns>
bool Drop(string slot);
/// <summary>
/// Drops an item held by one of our hand slots to the same position as our owning entity.
/// </summary>
/// <param name="entity">The item to drop.</param>
/// <returns>True on success, false if something blocked the drop.</returns>
/// <exception cref="ArgumentNullException">
/// Thrown if <see cref="entity"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if <see cref="entity"/> is not actually held in any hand.
/// </exception>
bool Drop(IEntity entity);
/// <summary>
/// Drops the item in a slot.
/// </summary>
/// <param name="slot">The slot to drop the item from.</param>
/// <param name="coords"></param> /// <param name="coords"></param>
/// <returns>True if an item was successfully dropped, false otherwise.</returns> /// <returns>True if an item was dropped, false otherwise.</returns>
bool Drop(string index, GridLocalCoordinates? coords); bool Drop(string slot, GridLocalCoordinates coords);
/// <summary>
/// Drop the specified entity in our hands to a certain position.
/// </summary>
/// <remarks>
/// There are no checks whether or not the user is within interaction range of the drop location
/// or whether the drop location is occupied.
/// </remarks>
/// <param name="entity">The entity to drop, must be held in one of the hands.</param>
/// <param name="coords">The coordinates to drop the entity at.</param>
/// <returns>
/// True if the drop succeeded,
/// false if it failed (due to failing to eject from our hand slot, etc...)
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown if <see cref="entity"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if <see cref="entity"/> is not actually held in any hand.
/// </exception>
bool Drop(IEntity entity, GridLocalCoordinates coords);
/// <summary>
/// Drop the item contained in a slot into another container.
/// </summary>
/// <param name="slot">The slot of which to drop the entity.</param>
/// <param name="targetContainer">The container to drop into.</param>
/// <returns>True on success, false if something was blocked (insertion or removal).</returns>
/// <exception cref="InvalidOperationException">
/// Thrown if dry-run checks reported OK to remove and insert,
/// but practical remove or insert returned false anyways.
/// This is an edge-case that is currently unhandled.
/// </exception>
bool Drop(string slot, BaseContainer targetContainer);
/// <summary>
/// Drops an item in one of the hands into a container.
/// </summary>
/// <param name="entity">The item to drop.</param>
/// <param name="targetContainer">The container to drop into.</param>
/// <returns>True on success, false if something was blocked (insertion or removal).</returns>
/// <exception cref="InvalidOperationException">
/// Thrown if dry-run checks reported OK to remove and insert,
/// but practical remove or insert returned false anyways.
/// This is an edge-case that is currently unhandled.
/// </exception>
/// <exception cref="ArgumentNullException">
/// Thrown if <see cref="entity"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if <see cref="entity"/> is not actually held in any hand.
/// </exception>
bool Drop(IEntity entity, BaseContainer targetContainer);
/// <summary> /// <summary>
/// Checks whether the item in the specified hand can be dropped. /// Checks whether the item in the specified hand can be dropped.

View File

@@ -70,7 +70,9 @@
<Compile Include="GameObjects\Components\Power\SharedPowerCellComponent.cs" /> <Compile Include="GameObjects\Components\Power\SharedPowerCellComponent.cs" />
<Compile Include="GameObjects\Components\Storage\SharedStorageComponent.cs" /> <Compile Include="GameObjects\Components\Storage\SharedStorageComponent.cs" />
<Compile Include="GameObjects\ContentNetIDs.cs" /> <Compile Include="GameObjects\ContentNetIDs.cs" />
<Compile Include="GameObjects\EntitySystemMessages\VerbSystemMessages.cs" />
<Compile Include="GameObjects\PhysicalConstants.cs" /> <Compile Include="GameObjects\PhysicalConstants.cs" />
<Compile Include="GameObjects\Verb.cs" />
<Compile Include="Physics\CollisionGroup.cs" /> <Compile Include="Physics\CollisionGroup.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="GameObjects\Components\Items\SharedHandsComponent.cs" /> <Compile Include="GameObjects\Components\Items\SharedHandsComponent.cs" />

View File

@@ -37,4 +37,16 @@ namespace Content.Shared.GameObjects
Directed = true; Directed = true;
} }
} }
[Serializable, NetSerializable]
public class ClientAttackByInHandMsg : ComponentMessage
{
public string Index { get; }
public ClientAttackByInHandMsg(string index)
{
Directed = true;
Index = index;
}
}
} }

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using SS14.Shared.GameObjects;
using SS14.Shared.Serialization;
namespace Content.Shared.GameObjects.EntitySystemMessages
{
public static class VerbSystemMessages
{
[Serializable, NetSerializable]
public class RequestVerbsMessage : EntitySystemMessage
{
public readonly EntityUid EntityUid;
public RequestVerbsMessage(EntityUid entityUid)
{
EntityUid = entityUid;
}
}
[Serializable, NetSerializable]
public class VerbsResponseMessage : EntitySystemMessage
{
public readonly List<VerbData> Verbs;
public readonly EntityUid Entity;
public VerbsResponseMessage(List<VerbData> verbs, EntityUid entity)
{
Verbs = verbs;
Entity = entity;
}
[Serializable, NetSerializable]
public readonly struct VerbData
{
public readonly string Text;
public readonly string Key;
public readonly bool Available;
public VerbData(string text, string key, bool available)
{
Text = text;
Key = key;
Available = available;
}
}
}
[Serializable, NetSerializable]
public class UseVerbMessage : EntitySystemMessage
{
public readonly EntityUid EntityUid;
public readonly string VerbKey;
public UseVerbMessage(EntityUid entityUid, string verbKey)
{
EntityUid = entityUid;
VerbKey = verbKey;
}
}
}
}

View File

@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using SS14.Shared.Interfaces.GameObjects;
namespace Content.Shared.GameObjects
{
/// <summary>
/// A verb is an action in the right click menu of an entity.
/// </summary>
/// <remarks>
/// To add a verb to an entity, define it as a nested class inside the owning component,
/// and mark it with <see cref="VerbAttribute"/>
/// </remarks>
[UsedImplicitly]
public abstract class Verb
{
/// <summary>
/// If true, this verb requires the user to be inside within
/// <see cref="InteractionRange"/> meters from the entity on which this verb resides.
/// </summary>
public virtual bool RequireInteractionRange => true;
public const float InteractionRange = 2;
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
/// <summary>
/// Gets the text string that will be shown to <paramref name="user"/> in the right click menu.
/// </summary>
/// <param name="user">The entity of the user opening this menu.</param>
/// <param name="component">The component instance for which this verb is being loaded.</param>
/// <returns>The text string that is shown in the right click menu for this verb.</returns>
public abstract string GetText(IEntity user, IComponent component);
/// <summary>
/// Gets whether this verb is "disabled" in the right click menu.
/// The verb is still visible in disabled state, but greyed out.
/// </summary>
/// <param name="user">The entity of the user opening this menu.</param>
/// <param name="component">The component instance for which this verb is being loaded.</param>
/// <returns>True if the verb is disabled, false otherwise.</returns>
public abstract bool IsDisabled(IEntity user, IComponent component);
/// <summary>
/// Invoked when this verb is activated from the right click menu.
/// </summary>
/// <param name="user">The entity of the user opening this menu.</param>
/// <param name="component">The component instance for which this verb is being loaded.</param>
public abstract void Activate(IEntity user, IComponent component);
}
/// <inheritdoc />
/// <summary>
/// Sub class of <see cref="T:Content.Shared.GameObjects.Verb" /> that works on a specific type of component,
/// to reduce casting boiler plate for implementations.
/// </summary>
/// <typeparam name="T">The type of component that this verb will run on.</typeparam>
public abstract class Verb<T> : Verb where T : IComponent
{
/// <summary>
/// Gets the text string that will be shown to <paramref name="user"/> in the right click menu.
/// </summary>
/// <param name="user">The entity of the user opening this menu.</param>
/// <param name="component">The component instance for which this verb is being loaded.</param>
/// <returns>The text string that is shown in the right click menu for this verb.</returns>
protected abstract string GetText(IEntity user, T component);
/// <summary>
/// Gets whether this verb is "disabled" in the right click menu.
/// The verb is still visible in disabled state, but greyed out.
/// </summary>
/// <param name="user">The entity of the user opening this menu.</param>
/// <param name="component">The component instance for which this verb is being loaded.</param>
/// <returns>True if the verb is disabled, false otherwise.</returns>
protected abstract bool IsDisabled(IEntity user, T component);
/// <summary>
/// Invoked when this verb is activated from the right click menu.
/// </summary>
/// <param name="user">The entity of the user opening this menu.</param>
/// <param name="component">The component instance for which this verb is being loaded.</param>
protected abstract void Activate(IEntity user, T component);
public sealed override string GetText(IEntity user, IComponent component)
{
return GetText(user, (T) component);
}
public sealed override bool IsDisabled(IEntity user, IComponent component)
{
return IsDisabled(user, (T) component);
}
public sealed override void Activate(IEntity user, IComponent component)
{
Activate(user, (T) component);
}
}
/// <summary>
/// This attribute should be used on <see cref="Verb"/> implementations nested inside component classes,
/// so that they're automatically detected.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class VerbAttribute : Attribute
{
}
public static class VerbUtility
{
// TODO: This is a quick hack. Verb objects should absolutely be cached properly.
// This works for now though.
public static IEnumerable<(IComponent, Verb)> GetVerbs(IEntity entity)
{
foreach (var component in entity.GetAllComponents())
{
var type = component.GetType();
foreach (var nestedType in type.GetNestedTypes())
{
if (!typeof(Verb).IsAssignableFrom(nestedType) || nestedType.IsAbstract)
{
continue;
}
var verb = (Verb) Activator.CreateInstance(nestedType);
yield return (component, verb);
}
}
}
}
}

View File

@@ -13,5 +13,6 @@ namespace Content.Shared.Input
public static readonly BoundKeyFunction UseItemInHand = "UseItemInHand"; // use hand item on world entity public static readonly BoundKeyFunction UseItemInHand = "UseItemInHand"; // use hand item on world entity
public static readonly BoundKeyFunction ActivateItemInWorld = "ActivateItemInWorld"; // default action on world entity public static readonly BoundKeyFunction ActivateItemInWorld = "ActivateItemInWorld"; // default action on world entity
public static readonly BoundKeyFunction ThrowItemInHand = "ThrowItemInHand"; public static readonly BoundKeyFunction ThrowItemInHand = "ThrowItemInHand";
public static readonly BoundKeyFunction OpenContextMenu = "OpenContextMenu";
} }
} }

View File

@@ -26,3 +26,6 @@ binds:
key: MouseLeft key: MouseLeft
mod1: Control mod1: Control
type: state type: state
- function: OpenContextMenu
key: MouseRight
type: state

2
engine

Submodule engine updated: 5ae665c3d2...69c0d409c8