Refactor drag and drop to use a shared interface (#2012)

* WIP in progress hours

* Cleanup

* Fix bugle

* Fix nullable error

* Merge fixes

* Merge fixes

* Merge fixes
This commit is contained in:
DrSmugleaf
2020-10-14 15:24:07 +02:00
committed by GitHub
parent f715eed63c
commit cdedaeb12e
37 changed files with 527 additions and 377 deletions

View File

@@ -1,17 +1,17 @@
#nullable enable
using Content.Client.GameObjects.Components.Disposal;
using Content.Client.GameObjects.Components.MedicalScanner;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Body
{
[RegisterComponent]
[ComponentReference(typeof(IBody))]
public class BodyComponent : SharedBodyComponent, IClientDraggable
public class BodyComponent : SharedBodyComponent, IDraggable
{
public bool ClientCanDropOn(CanDropEventArgs eventArgs)
public bool CanDrop(CanDropEventArgs eventArgs)
{
if (eventArgs.Target.HasComponent<DisposalUnitComponent>() ||
eventArgs.Target.HasComponent<MedicalScannerComponent>())
@@ -21,10 +21,5 @@ namespace Content.Client.GameObjects.Components.Body
return false;
}
public bool ClientCanDrag(CanDragEventArgs eventArgs)
{
return true;
}
}
}

View File

@@ -1,19 +1,24 @@
using Content.Client.GameObjects.Components.Strap;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects.Components.Buckle;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Buckle
{
[RegisterComponent]
public class BuckleComponent : SharedBuckleComponent, IClientDraggable
public class BuckleComponent : SharedBuckleComponent
{
private bool _buckled;
private int? _originalDrawDepth;
public override bool Buckled => _buckled;
public override bool TryBuckle(IEntity user, IEntity to)
{
// TODO: Prediction
return false;
}
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
if (!(curState is BuckleComponentState buckle))
@@ -41,15 +46,5 @@ namespace Content.Client.GameObjects.Components.Buckle
_originalDrawDepth = null;
}
}
bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
{
return eventArgs.Target.HasComponent<StrapComponent>();
}
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
{
return true;
}
}
}

View File

@@ -1,22 +1,17 @@
using Content.Client.GameObjects.Components.Items;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects.Components.GUI;
using Content.Shared.GameObjects.Components.GUI;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.GUI
{
[RegisterComponent]
public class StrippableComponent : SharedStrippableComponent, IClientDraggable
[ComponentReference(typeof(SharedStrippableComponent))]
public class StrippableComponent : SharedStrippableComponent
{
public bool ClientCanDropOn(CanDropEventArgs eventArgs)
public override bool Drop(DragDropEventArgs args)
{
return eventArgs.Target.HasComponent<HandsComponent>()
&& eventArgs.Target != eventArgs.Dragged && eventArgs.Target == eventArgs.User;
}
public bool ClientCanDrag(CanDragEventArgs eventArgs)
{
return true;
// TODO: Prediction
return false;
}
}
}

View File

@@ -1,7 +1,7 @@
using Content.Client.GameObjects.Components.Disposal;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Items;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Client.Graphics;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.ResourceManagement;
@@ -19,7 +19,7 @@ namespace Content.Client.GameObjects.Components.Items
{
[RegisterComponent]
[ComponentReference(typeof(IItemComponent))]
public class ItemComponent : Component, IItemComponent, IClientDraggable
public class ItemComponent : Component, IItemComponent, IDraggable
{
public override string Name => "Item";
public override uint? NetID => ContentNetIDs.ITEM;
@@ -85,14 +85,15 @@ namespace Content.Client.GameObjects.Components.Items
EquippedPrefix = itemComponentState.EquippedPrefix;
}
bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
bool IDraggable.CanDrop(CanDropEventArgs args)
{
return eventArgs.Target.HasComponent<DisposalUnitComponent>();
return args.Target.HasComponent<DisposalUnitComponent>();
}
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
public bool Drop(DragDropEventArgs args)
{
return true;
// TODO: Shared item class
return false;
}
}
}

View File

@@ -1,11 +1,10 @@
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.GameObjects.Components.Movement;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Movement
{
[RegisterComponent]
public class ClimbingComponent : SharedClimbingComponent, IClientDraggable
public class ClimbingComponent : SharedClimbingComponent
{
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
@@ -18,15 +17,5 @@ namespace Content.Client.GameObjects.Components.Movement
}
public override bool IsClimbing { get; set; }
bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
{
return eventArgs.Target.HasComponent<IClimbable>();
}
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
{
return true;
}
}
}

View File

@@ -1,11 +1,41 @@
#nullable enable
using Content.Shared.GameObjects.Components;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components
{
[RegisterComponent]
[ComponentReference(typeof(SharedPlaceableSurfaceComponent))]
public class PlaceableSurfaceComponent : SharedPlaceableSurfaceComponent
{
private bool _isPlaceable;
public override bool IsPlaceable
{
get => _isPlaceable;
set
{
if (_isPlaceable == value)
{
return;
}
_isPlaceable = value;
Dirty();
}
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
base.HandleComponentState(curState, nextState);
if (!(curState is PlaceableSurfaceComponentState state))
{
return;
}
_isPlaceable = state.IsPlaceable;
}
}
}

View File

@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.GameObjects.Components.Items;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects.Components.Storage;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Player;
@@ -22,13 +23,17 @@ namespace Content.Client.GameObjects.Components.Storage
/// Client version of item storage containers, contains a UI which displays stored entities and their size
/// </summary>
[RegisterComponent]
public class ClientStorageComponent : SharedStorageComponent, IClientDraggable
public class ClientStorageComponent : SharedStorageComponent, IDraggable
{
private Dictionary<EntityUid, int> StoredEntities { get; set; } = new Dictionary<EntityUid, int>();
[Dependency] private readonly IEntityManager _entityManager = default!;
private List<IEntity> _storedEntities = new List<IEntity>();
private int StorageSizeUsed;
private int StorageCapacityMax;
private StorageWindow Window;
public override IReadOnlyList<IEntity> StoredEntities => _storedEntities;
public override void OnAdd()
{
base.OnAdd();
@@ -43,6 +48,20 @@ namespace Content.Client.GameObjects.Components.Storage
base.OnRemove();
}
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if (!(curState is StorageComponentState state))
{
return;
}
_storedEntities = state.StoredEntities
.Select(id => _entityManager.GetEntity(id))
.ToList();
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null)
{
base.HandleNetworkMessage(message, channel, session);
@@ -69,7 +88,7 @@ namespace Content.Client.GameObjects.Components.Storage
/// <param name="storageState"></param>
private void HandleStorageMessage(StorageHeldItemsMessage storageState)
{
StoredEntities = new Dictionary<EntityUid, int>(storageState.StoredEntities);
_storedEntities = storageState.StoredEntities.Select(id => _entityManager.GetEntity(id)).ToList();
StorageSizeUsed = storageState.StorageSizeUsed;
StorageCapacityMax = storageState.StorageSizeMax;
Window.BuildEntityList();
@@ -100,6 +119,17 @@ namespace Content.Client.GameObjects.Components.Storage
SendNetworkMessage(new RemoveEntityMessage(entityUid));
}
public override bool Remove(IEntity entity)
{
if (_storedEntities.Remove(entity))
{
Dirty();
return true;
}
return false;
}
/// <summary>
/// GUI class for client storage component
/// </summary>
@@ -200,19 +230,25 @@ namespace Content.Client.GameObjects.Components.Storage
var storageList = StorageEntity.StoredEntities;
foreach (var entityUid in storageList)
var storedGrouped = storageList.GroupBy(e => e).Select(e => new
{
var entity = IoCManager.Resolve<IEntityManager>().GetEntity(entityUid.Key);
Entity = e.Key,
Amount = e.Count()
});
foreach (var group in storedGrouped)
{
var entity = group.Entity;
var button = new EntityButton()
{
EntityUid = entityUid.Key,
EntityUid = entity.Uid,
MouseFilter = MouseFilterMode.Stop,
};
button.ActualButton.OnToggled += OnItemButtonToggled;
//Name and Size labels set
button.EntityName.Text = entity.Name;
button.EntitySize.Text = string.Format("{0}", entityUid.Value);
button.EntitySize.Text = group.Amount.ToString();
//Gets entity sprite and assigns it to button texture
if (entity.TryGetComponent(out ISpriteComponent sprite))
@@ -320,17 +356,5 @@ namespace Content.Client.GameObjects.Components.Storage
AddChild(hBoxContainer);
}
}
public bool ClientCanDropOn(CanDropEventArgs eventArgs)
{
//can only drop on placeable surfaces to empty out contents
return eventArgs.Target.HasComponent<PlaceableSurfaceComponent>();
}
public bool ClientCanDrag(CanDragEventArgs eventArgs)
{
//always draggable, at least for now
return true;
}
}
}

View File

@@ -0,0 +1,41 @@
#nullable enable
using Content.Shared.GameObjects.Components.Storage;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Storage
{
[RegisterComponent]
[ComponentReference(typeof(SharedStorableComponent))]
public class StorableComponent : SharedStorableComponent
{
private int _size;
public override int Size
{
get => _size;
set
{
if (_size == value)
{
return;
}
_size = value;
Dirty();
}
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
base.HandleComponentState(curState, nextState);
if (!(curState is StorableComponentState state))
{
return;
}
_size = state.Size;
}
}
}

View File

@@ -5,6 +5,7 @@ using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Strap
{
[RegisterComponent]
[ComponentReference(typeof(SharedStrapComponent))]
public class StrapComponent : SharedStrapComponent
{
}

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Client.State;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.EntitySystemMessages;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.EntitySystems;
@@ -52,7 +52,7 @@ namespace Content.Client.GameObjects.EntitySystems
// entity performing the drag action
private IEntity _dragger;
private IEntity _draggedEntity;
private readonly List<IClientDraggable> _draggables = new List<IClientDraggable>();
private readonly List<IDraggable> _draggables = new List<IDraggable>();
private IEntity _dragShadow;
private DragState _state;
// time since mouse down over the dragged entity
@@ -146,10 +146,10 @@ namespace Content.Client.GameObjects.EntitySystems
}
var canDrag = false;
foreach (var draggable in entity.GetAllComponents<IClientDraggable>())
foreach (var draggable in entity.GetAllComponents<IDraggable>())
{
var dragEventArgs = new CanDragEventArgs(args.Session.AttachedEntity, entity);
if (draggable.ClientCanDrag(dragEventArgs))
var dragEventArgs = new StartDragDropEventArgs(args.Session.AttachedEntity, entity);
if (draggable.CanStartDrag(dragEventArgs))
{
// wait to initiate a drag
_dragger = dragger;
@@ -202,19 +202,26 @@ namespace Content.Client.GameObjects.EntitySystems
foreach (var entity in entities)
{
// check if it's able to be dropped on by current dragged entity
var canDropArgs = new CanDropEventArgs(_dragger, _draggedEntity, entity);
var anyValidDraggable = _draggables.Any(draggable => draggable.ClientCanDropOn(canDropArgs));
var dropArgs = new DragDropEventArgs(_dragger, args.Coordinates, _draggedEntity, entity);
if (anyValidDraggable)
foreach (var draggable in _draggables)
{
if (!draggable.CanDrop(dropArgs))
{
continue;
}
// tell the server about the drop attempt
RaiseNetworkEvent(new DragDropMessage(args.Coordinates, _draggedEntity.Uid,
entity.Uid));
draggable.Drop(dropArgs);
CancelDrag(false, null);
return true;
}
}
CancelDrag(false, null);
return false;
}
@@ -283,8 +290,8 @@ namespace Content.Client.GameObjects.EntitySystems
if (inRangeSprite.Visible == false) continue;
// check if it's able to be dropped on by current dragged entity
var canDropArgs = new CanDropEventArgs(_dragger, _draggedEntity, pvsEntity);
var anyValidDraggable = _draggables.Any(draggable => draggable.ClientCanDropOn(canDropArgs));
var canDropArgs = new CanDropEventArgs(_dragger, _draggedEntity, pvsEntity);
var anyValidDraggable = _draggables.Any(draggable => draggable.CanDrop(canDropArgs));
if (anyValidDraggable)
{

View File

@@ -28,7 +28,6 @@
"Wrench",
"Crowbar",
"MeleeWeapon",
"Storable",
"Dice",
"Construction",
"Door",

View File

@@ -1,88 +0,0 @@
using System;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.Interfaces.GameObjects.Components.Interaction
{
/// <summary>
/// This interface allows a local client to initiate dragging of the component's entity by mouse, for drag and
/// drop interactions. The actual logic of what happens on drop
/// is handled by IDragDrop
/// </summary>
public interface IClientDraggable
{
/// <summary>
/// Invoked on entities visible to the user to check if this component's entity
/// can be dropped on the indicated target entity. No need to check range / reachability in here.
/// </summary>
/// <returns>true iff target is a valid target to be dropped on by this
/// component's entity. Returning true will cause the target entity to be highlighted as a potential
/// target and allow dropping when in range.</returns>
bool ClientCanDropOn(CanDropEventArgs eventArgs);
/// <summary>
/// Invoked clientside when user is attempting to initiate a drag with this component's entity
/// in range. Return true if the drag should be initiated. It's fine to
/// return true even if there wouldn't be any valid targets - just return true
/// if this entity is in a "draggable" state.
/// </summary>
/// <param name="eventArgs"></param>
/// <returns>true iff drag should be initiated</returns>
bool ClientCanDrag(CanDragEventArgs eventArgs);
}
public class CanDropEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of <see cref="CanDropEventArgs"/>.
/// </summary>
/// <param name="user">The entity doing the drag and drop.</param>
/// <param name="dragged">The entity that is being dragged and dropped.</param>
/// <param name="target">The entity that <see cref="dragged"/> is being dropped onto.</param>
public CanDropEventArgs(IEntity user, IEntity dragged, IEntity target)
{
User = user;
Dragged = dragged;
Target = target;
}
/// <summary>
/// The entity doing the drag and drop.
/// </summary>
public IEntity User { get; }
/// <summary>
/// The entity that is being dragged and dropped.
/// </summary>
public IEntity Dragged { get; }
/// <summary>
/// The entity that <see cref="Dragged"/> is being dropped onto.
/// </summary>
public IEntity Target { get; }
}
public class CanDragEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of <see cref="CanDragEventArgs"/>.
/// </summary>
/// <param name="user">The entity doing the drag and drop.</param>
/// <param name="dragged">The entity that is being dragged and dropped.</param>
public CanDragEventArgs(IEntity user, IEntity dragged)
{
User = user;
Dragged = dragged;
}
/// <summary>
/// The entity doing the drag and drop.
/// </summary>
public IEntity User { get; }
/// <summary>
/// The entity that is being dragged and dropped.
/// </summary>
public IEntity Dragged { get; }
}
}

View File

@@ -34,7 +34,7 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Buckle
{
[RegisterComponent]
public class BuckleComponent : SharedBuckleComponent, IInteractHand, IDragDrop
public class BuckleComponent : SharedBuckleComponent, IInteractHand
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
@@ -256,18 +256,7 @@ namespace Content.Server.GameObjects.Components.Buckle
return true;
}
/// <summary>
/// Tries to make an entity buckle the owner of this component to another.
/// </summary>
/// <param name="user">
/// The entity buckling the owner of this component, can be the owner itself.
/// </param>
/// <param name="to">The entity to buckle the owner of this component to.</param>
/// <returns>
/// true if the owner was buckled, otherwise false even if the owner was
/// previously already buckled.
/// </returns>
public bool TryBuckle(IEntity user, IEntity to)
public override bool TryBuckle(IEntity user, IEntity to)
{
if (!CanBuckle(user, to, out var strap))
{
@@ -544,16 +533,6 @@ namespace Content.Server.GameObjects.Components.Buckle
return TryUnbuckle(eventArgs.User);
}
bool IDragDrop.CanDragDrop(DragDropEventArgs eventArgs)
{
return eventArgs.Target.HasComponent<StrapComponent>();
}
bool IDragDrop.DragDrop(DragDropEventArgs eventArgs)
{
return TryBuckle(eventArgs.User, eventArgs.Target);
}
/// <summary>
/// Allows the unbuckling of the owning entity through a verb if
/// anyone right clicks them.

View File

@@ -674,12 +674,12 @@ namespace Content.Server.GameObjects.Components.Disposal
bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs)
{
return CanInsert(eventArgs.Dropped);
return CanInsert(eventArgs.Dragged);
}
bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
{
_ = TryInsert(eventArgs.Dropped, eventArgs.User);
_ = TryInsert(eventArgs.Dragged, eventArgs.User);
return true;
}

View File

@@ -45,7 +45,7 @@ namespace Content.Server.GameObjects.Components.GUI
var itemComponent = entity.GetComponent<ItemComponent>();
// If this item is small enough then it always fits in pockets.
if (itemComponent.ObjectSize <= (int) ReferenceSizes.Pocket)
if (itemComponent.Size <= (int) ReferenceSizes.Pocket)
{
return true;
}

View File

@@ -23,7 +23,7 @@ using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefine
namespace Content.Server.GameObjects.Components.GUI
{
[RegisterComponent]
public sealed class StrippableComponent : SharedStrippableComponent, IDragDrop
public sealed class StrippableComponent : SharedStrippableComponent
{
public const float StripDelay = 2f;
@@ -75,23 +75,9 @@ namespace Content.Server.GameObjects.Components.GUI
UserInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands, cuffs));
}
public bool CanBeStripped(IEntity by)
public override bool Drop(DragDropEventArgs args)
{
return by != Owner
&& by.HasComponent<HandsComponent>()
&& ActionBlockerSystem.CanInteract(by);
}
public bool CanDragDrop(DragDropEventArgs eventArgs)
{
return eventArgs.Target != eventArgs.Dropped
&& eventArgs.Target == eventArgs.User
&& CanBeStripped(eventArgs.User);
}
public bool DragDrop(DragDropEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false;
if (!args.User.TryGetComponent(out IActorComponent? actor)) return false;
OpenUserInterface(actor.playerSession);
return true;

View File

@@ -4,6 +4,7 @@ using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Items;
using Content.Shared.GameObjects.Components.Storage;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
@@ -17,6 +18,7 @@ namespace Content.Server.GameObjects.Components.Items.Clothing
[RegisterComponent]
[ComponentReference(typeof(ItemComponent))]
[ComponentReference(typeof(StorableComponent))]
[ComponentReference(typeof(SharedStorableComponent))]
[ComponentReference(typeof(IItemComponent))]
public class ClothingComponent : ItemComponent, IUse
{

View File

@@ -3,6 +3,7 @@ using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Throw;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Items;
using Content.Shared.GameObjects.Components.Storage;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -19,6 +20,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
{
[RegisterComponent]
[ComponentReference(typeof(StorableComponent))]
[ComponentReference(typeof(SharedStorableComponent))]
[ComponentReference(typeof(IItemComponent))]
public class ItemComponent : StorableComponent, IInteractHand, IExAct, IEquipped, IUnequipped, IItemComponent
{

View File

@@ -34,8 +34,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IStorageComponent))]
public class ServerStorageComponent : SharedStorageComponent, IInteractUsing, IUse, IActivate, IStorageComponent, IDestroyAct, IExAct,
IDragDrop
public class ServerStorageComponent : SharedStorageComponent, IInteractUsing, IUse, IActivate, IStorageComponent, IDestroyAct, IExAct
{
[Dependency] private readonly IEntityManager _entityManager = default!;
@@ -50,7 +49,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
public readonly HashSet<IPlayerSession> SubscribedSessions = new HashSet<IPlayerSession>();
[ViewVariables]
public IReadOnlyCollection<IEntity>? StoredEntities => _storage?.ContainedEntities;
public override IReadOnlyList<IEntity>? StoredEntities => _storage?.ContainedEntities;
[ViewVariables(VVAccess.ReadWrite)]
public bool OccludesLight
@@ -87,7 +86,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
foreach (var entity in _storage.ContainedEntities)
{
var item = entity.GetComponent<StorableComponent>();
_storageUsed += item.ObjectSize;
_storageUsed += item.Size;
}
}
@@ -107,7 +106,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
}
if (entity.TryGetComponent(out StorableComponent? store) &&
store.ObjectSize > _storageCapacityMax - _storageUsed)
store.Size > _storageCapacityMax - _storageUsed)
{
return false;
}
@@ -125,12 +124,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
return CanInsert(entity) && _storage?.Insert(entity) == true;
}
/// <summary>
/// Removes from the storage container and updates the stored value
/// </summary>
/// <param name="entity">The entity to remove</param>
/// <returns>true if no longer in storage, false otherwise</returns>
public bool Remove(IEntity entity)
public override bool Remove(IEntity entity)
{
EnsureInitialCalculated();
return _storage?.Remove(entity) == true;
@@ -147,7 +141,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
Logger.DebugS(LoggerName, $"Storage (UID {Owner.Uid}) had entity (UID {message.Entity.Uid}) inserted into it.");
_storageUsed += message.Entity.GetComponent<StorableComponent>().ObjectSize;
_storageUsed += message.Entity.GetComponent<StorableComponent>().Size;
UpdateClientInventories();
}
@@ -171,7 +165,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
return;
}
_storageUsed -= storable.ObjectSize;
_storageUsed -= storable.Size;
UpdateClientInventories();
}
@@ -258,14 +252,16 @@ namespace Content.Server.GameObjects.Components.Items.Storage
return;
}
var storedEntities = new Dictionary<EntityUid, int>();
foreach (var entities in _storage.ContainedEntities)
if (StoredEntities == null)
{
storedEntities.Add(entities.Uid, entities.GetComponent<StorableComponent>().ObjectSize);
Logger.WarningS(LoggerName, $"{nameof(UpdateClientInventory)} called with null {nameof(StoredEntities)}");
return;
}
SendNetworkMessage(new StorageHeldItemsMessage(storedEntities, _storageUsed, _storageCapacityMax), session.ConnectedClient);
var stored = StoredEntities.Select(e => e.Uid).ToArray();
SendNetworkMessage(new StorageHeldItemsMessage(stored, _storageUsed, _storageCapacityMax), session.ConnectedClient);
}
/// <summary>
@@ -500,43 +496,5 @@ namespace Content.Server.GameObjects.Components.Items.Storage
}
}
}
bool IDragDrop.CanDragDrop(DragDropEventArgs eventArgs)
{
return eventArgs.Target.TryGetComponent(out PlaceableSurfaceComponent? placeable) &&
placeable.IsPlaceable;
}
bool IDragDrop.DragDrop(DragDropEventArgs eventArgs)
{
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
{
return false;
}
if (!eventArgs.Target.TryGetComponent<PlaceableSurfaceComponent>(out var placeableSurface) ||
!placeableSurface.IsPlaceable)
{
return false;
}
var storedEntities = StoredEntities?.ToList();
if (storedEntities == null)
{
return false;
}
// empty everything out
foreach (var storedEntity in StoredEntities.ToList())
{
if (Remove(storedEntity))
{
storedEntity.Transform.WorldPosition = eventArgs.DropLocation.Position;
}
}
return true;
}
}
}

View File

@@ -1,20 +1,33 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Content.Shared.GameObjects.Components.Storage;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Items.Storage
{
[RegisterComponent]
public class StorableComponent : Component
[ComponentReference(typeof(SharedStorableComponent))]
public class StorableComponent : SharedStorableComponent
{
public override string Name => "Storable";
private int _size;
public int ObjectSize;
public override void ExposeData(ObjectSerializer serializer)
public override int Size
{
base.ExposeData(serializer);
get => _size;
set
{
if (_size == value)
{
return;
}
serializer.DataField(ref ObjectSize, "size", 1);
_size = value;
Dirty();
}
}
public override ComponentState GetComponentState()
{
return new StorableComponentState(_size);
}
}

View File

@@ -31,6 +31,7 @@ namespace Content.Server.GameObjects.Components.Medical
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(SharedMedicalScannerComponent))]
public class MedicalScannerComponent : SharedMedicalScannerComponent, IActivate, IDragDropOn
{
private ContainerSlot _bodyContainer = default!;
@@ -250,12 +251,12 @@ namespace Content.Server.GameObjects.Components.Medical
public bool CanDragDropOn(DragDropEventArgs eventArgs)
{
return eventArgs.Dropped.HasComponent<IBody>();
return eventArgs.Dragged.HasComponent<IBody>();
}
public bool DragDropOn(DragDropEventArgs eventArgs)
{
_bodyContainer.Insert(eventArgs.Dropped);
_bodyContainer.Insert(eventArgs.Dragged);
return true;
}
}

View File

@@ -67,10 +67,10 @@ namespace Content.Server.GameObjects.Components.Movement
string reason;
bool canVault;
if (eventArgs.User == eventArgs.Dropped)
if (eventArgs.User == eventArgs.Dragged)
canVault = CanVault(eventArgs.User, eventArgs.Target, out reason);
else
canVault = CanVault(eventArgs.User, eventArgs.Dropped, eventArgs.Target, out reason);
canVault = CanVault(eventArgs.User, eventArgs.Dragged, eventArgs.Target, out reason);
if (!canVault)
eventArgs.User.PopupMessage(reason);
@@ -154,13 +154,13 @@ namespace Content.Server.GameObjects.Components.Movement
bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
{
if (eventArgs.User == eventArgs.Dropped)
if (eventArgs.User == eventArgs.Dragged)
{
TryClimb(eventArgs.User);
}
else
{
TryMoveEntity(eventArgs.User, eventArgs.Dropped);
TryMoveEntity(eventArgs.User, eventArgs.Dragged);
}
return true;

View File

@@ -1,5 +1,4 @@
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Physics;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
@@ -7,7 +6,7 @@ using Robust.Shared.Maths;
namespace Content.Server.GameObjects.Components.Movement
{
[RegisterComponent]
public class ClimbingComponent : SharedClimbingComponent, IActionBlocker
public class ClimbingComponent : SharedClimbingComponent
{
private bool _isClimbing = false;
private ClimbController _climbController = default;

View File

@@ -9,11 +9,27 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components
{
[RegisterComponent]
[ComponentReference(typeof(SharedPlaceableSurfaceComponent))]
public class PlaceableSurfaceComponent : SharedPlaceableSurfaceComponent, IInteractUsing
{
private bool _isPlaceable;
[ViewVariables(VVAccess.ReadWrite)]
public bool IsPlaceable { get => _isPlaceable; set => _isPlaceable = value; }
public override bool IsPlaceable
{
get => _isPlaceable;
set
{
if (_isPlaceable == value)
{
return;
}
_isPlaceable = value;
Dirty();
}
}
[ViewVariables]
int IInteractUsing.Priority => 1;
@@ -25,6 +41,10 @@ namespace Content.Server.GameObjects.Components
serializer.DataField(ref _isPlaceable, "IsPlaceable", true);
}
public override ComponentState GetComponentState()
{
return new PlaceableSurfaceComponentState(_isPlaceable);
}
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
{

View File

@@ -41,7 +41,7 @@ namespace Content.Server.GameObjects.Components
return false;
}
var size = eventArgs.Using.GetComponent<ItemComponent>().ObjectSize;
var size = eventArgs.Using.GetComponent<ItemComponent>().Size;
// TODO: use proper text macro system for this.

View File

@@ -18,6 +18,7 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Strap
{
[RegisterComponent]
[ComponentReference(typeof(SharedStrapComponent))]
public class StrapComponent : SharedStrapComponent, IInteractHand
{
[ComponentDependency] public readonly SpriteComponent? SpriteComponent = null;

View File

@@ -75,10 +75,10 @@ namespace Content.Server.GameObjects.EntitySystems.Click
if (!interactionArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
// trigger dragdrops on the dropped entity
foreach (var dragDrop in dropped.GetAllComponents<IDragDrop>())
foreach (var dragDrop in dropped.GetAllComponents<IDraggable>())
{
if (dragDrop.CanDragDrop(interactionArgs) &&
dragDrop.DragDrop(interactionArgs))
if (dragDrop.CanDrop(interactionArgs) &&
dragDrop.Drop(interactionArgs))
{
return;
}

View File

@@ -1,12 +1,14 @@
using System;
using Content.Shared.GameObjects.Components.Strap;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Buckle
{
public abstract class SharedBuckleComponent : Component, IActionBlocker, IEffectBlocker
public abstract class SharedBuckleComponent : Component, IActionBlocker, IEffectBlocker, IDraggable
{
public sealed override string Name => "Buckle";
@@ -17,6 +19,8 @@ namespace Content.Shared.GameObjects.Components.Buckle
/// </summary>
public abstract bool Buckled { get; }
public abstract bool TryBuckle(IEntity user, IEntity to);
bool IActionBlocker.CanMove()
{
return !Buckled;
@@ -31,6 +35,16 @@ namespace Content.Shared.GameObjects.Components.Buckle
{
return !Buckled;
}
bool IDraggable.CanDrop(CanDropEventArgs args)
{
return args.Target.HasComponent<SharedStrapComponent>();
}
public bool Drop(DragDropEventArgs args)
{
return TryBuckle(args.User, args.Dragged);
}
}
[Serializable, NetSerializable]

View File

@@ -1,16 +1,36 @@
using System;
using System.Collections.Generic;
using Content.Shared.GameObjects.Components.Items;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines;
namespace Content.Shared.GameObjects.Components.GUI
{
public class SharedStrippableComponent : Component
public abstract class SharedStrippableComponent : Component, IDraggable
{
public override string Name => "Strippable";
public bool CanBeStripped(IEntity by)
{
return by != Owner
&& by.HasComponent<ISharedHandsComponent>()
&& ActionBlockerSystem.CanInteract(by);
}
bool IDraggable.CanDrop(CanDropEventArgs args)
{
return args.Target != args.Dragged
&& args.Target == args.User
&& CanBeStripped(args.User);
}
public abstract bool Drop(DragDropEventArgs args);
[NetSerializable, Serializable]
public enum StrippingUiKey
{

View File

@@ -5,10 +5,11 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.Physics;
using Robust.Shared.Serialization;
using Content.Shared.Interfaces.GameObjects.Components;
namespace Content.Shared.GameObjects.Components.Movement
{
public abstract class SharedClimbingComponent : Component, IActionBlocker, ICollideSpecial
public abstract class SharedClimbingComponent : Component, IActionBlocker, ICollideSpecial, IDraggable
{
public sealed override string Name => "Climbing";
public sealed override uint? NetID => ContentNetIDs.CLIMBING;
@@ -45,6 +46,16 @@ namespace Content.Shared.GameObjects.Components.Movement
return false;
}
bool IDraggable.CanDrop(CanDropEventArgs args)
{
return args.Target.HasComponent<IClimbable>();
}
bool IDraggable.Drop(DragDropEventArgs args)
{
return false;
}
public override void Initialize()
{
base.Initialize();

View File

@@ -1,9 +1,25 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components
{
public abstract class SharedPlaceableSurfaceComponent : Component
{
public override string Name => "PlaceableSurface";
public override uint? NetID => ContentNetIDs.PLACEABLE_SURFACE;
public virtual bool IsPlaceable { get; set; }
}
[Serializable, NetSerializable]
public class PlaceableSurfaceComponentState : ComponentState
{
public readonly bool IsPlaceable;
public PlaceableSurfaceComponentState(bool placeable) : base(ContentNetIDs.PLACEABLE_SURFACE)
{
IsPlaceable = placeable;
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Storage
{
public abstract class SharedStorableComponent : Component
{
public override string Name => "Storable";
public override uint? NetID => ContentNetIDs.STORABLE;
public virtual int Size { get; set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, s => s.Size, "size", 1);
}
}
[Serializable, NetSerializable]
public class StorableComponentState : ComponentState
{
public readonly int Size;
public StorableComponentState(int size) : base(ContentNetIDs.STORABLE)
{
Size = size;
}
}
}

View File

@@ -1,14 +1,71 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Storage
{
public abstract class SharedStorageComponent : Component
public abstract class SharedStorageComponent : Component, IDraggable
{
public override string Name => "Storage";
public override uint? NetID => ContentNetIDs.INVENTORY;
public abstract IReadOnlyList<IEntity>? StoredEntities { get; }
/// <summary>
/// Removes from the storage container and updates the stored value
/// </summary>
/// <param name="entity">The entity to remove</param>
/// <returns>True if no longer in storage, false otherwise</returns>
public abstract bool Remove(IEntity entity);
public bool CanDrop(CanDropEventArgs args)
{
return args.Target.TryGetComponent(out SharedPlaceableSurfaceComponent? placeable) &&
placeable.IsPlaceable;
}
public bool Drop(DragDropEventArgs eventArgs)
{
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
{
return false;
}
var storedEntities = StoredEntities?.ToArray();
if (storedEntities == null)
{
return false;
}
// empty everything out
foreach (var storedEntity in storedEntities)
{
if (Remove(storedEntity))
{
storedEntity.Transform.WorldPosition = eventArgs.DropLocation.Position;
}
}
return true;
}
}
[Serializable, NetSerializable]
public class StorageComponentState : ComponentState
{
public readonly EntityUid[] StoredEntities;
public StorageComponentState(EntityUid[] storedEntities) : base(ContentNetIDs.INVENTORY)
{
StoredEntities = storedEntities;
}
}
/// <summary>
@@ -19,14 +76,14 @@ namespace Content.Shared.GameObjects.Components.Storage
{
public readonly int StorageSizeMax;
public readonly int StorageSizeUsed;
public Dictionary<EntityUid, int> StoredEntities;
public readonly EntityUid[] StoredEntities;
public StorageHeldItemsMessage(Dictionary<EntityUid, int> storedentities, int storageused, int storagemaxsize)
public StorageHeldItemsMessage(EntityUid[] storedEntities, int storageUsed, int storageMaxSize)
{
Directed = true;
StorageSizeMax = storagemaxsize;
StorageSizeUsed = storageused;
StoredEntities = storedentities;
StorageSizeMax = storageMaxSize;
StorageSizeUsed = storageUsed;
StoredEntities = storedEntities;
}
}

View File

@@ -79,6 +79,8 @@
public const uint BLOCKGAME_ARCADE = 1073;
public const uint BODY_PART = 1074;
public const uint CRAYONS = 1075;
public const uint PLACEABLE_SURFACE = 1076;
public const uint STORABLE = 1077;
// Net IDs for integration tests.
public const uint PREDICTION_TEST = 10001;

View File

@@ -445,7 +445,7 @@ namespace Content.Shared.GameObjects.EntitySystems
bool popup = false)
{
var user = args.User;
var dropped = args.Dropped;
var dropped = args.Dragged;
var target = args.Target;
if (!InRangeUnobstructed(user, target, range, collisionMask, predicate, ignoreInsideBlocker))

View File

@@ -1,73 +0,0 @@
using System;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Map;
namespace Content.Shared.Interfaces.GameObjects.Components
{
/// <summary>
/// This interface allows the component's entity to be dragged and dropped
/// by mouse onto another entity and gives it behavior when that occurs.
/// </summary>
public interface IDragDrop
{
/// <summary>
/// Invoked server-side when this component's entity is being dragged
/// and dropped on another before invoking <see cref="DragDrop"/>.
/// Note that other drag and drop interactions may be attempted if
/// this one fails.
/// </summary>
/// <param name="eventArgs"></param>
/// <returns>true if <see cref="eventArgs"/> is valid, false otherwise.</returns>
bool CanDragDrop(DragDropEventArgs eventArgs);
/// <summary>
/// Invoked server-side when this component's entity is being dragged
/// and dropped on another.
/// Note that other drag and drop interactions may be attempted if
/// this one fails.
/// </summary>
/// <returns>
/// true if an interaction occurred and no further interaction should
/// be processed for this drop.
/// </returns>
bool DragDrop(DragDropEventArgs eventArgs);
}
public class DragDropEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of <see cref="DragDropEventArgs"/>.
/// </summary>
/// <param name="user">The entity doing the drag and drop.</param>
/// <param name="dropLocation">The location where <see cref="dropped"/> is being dropped.</param>
/// <param name="dropped">The entity that is being dragged and dropped.</param>
/// <param name="target">The entity that <see cref="dropped"/> is being dropped onto.</param>
public DragDropEventArgs(IEntity user, EntityCoordinates dropLocation, IEntity dropped, IEntity target)
{
User = user;
DropLocation = dropLocation;
Dropped = dropped;
Target = target;
}
/// <summary>
/// The entity doing the drag and drop.
/// </summary>
public IEntity User { get; }
/// <summary>
/// The location where <see cref="Dropped"/> is being dropped.
/// </summary>
public EntityCoordinates DropLocation { get; }
/// <summary>
/// The entity that is being dragged and dropped.
/// </summary>
public IEntity Dropped { get; }
/// <summary>
/// The entity that <see cref="Dropped"/> is being dropped onto.
/// </summary>
public IEntity Target { get; }
}
}

View File

@@ -0,0 +1,121 @@
using System;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Map;
namespace Content.Shared.Interfaces.GameObjects.Components
{
/// <summary>
/// This interface allows a local client to initiate dragging of the component's
/// entity by mouse, for drag and drop interactions.
/// </summary>
public interface IDraggable
{
/// <summary>
/// Invoked when an user is attempting to initiate a drag with
/// this component's entity in range. It's fine to return true even if there
/// wouldn't be any valid targets - just return true if this entity is in a
/// "draggable" state.
/// </summary>
/// <param name="args">
/// The information about the drag, such as who is doing it.
/// </param>
/// <returns>True if the drag should be initiated, false otherwise.</returns>
bool CanStartDrag(StartDragDropEventArgs args)
{
return true;
}
/// <summary>
/// Invoked on entities visible to the user to check if this component's
/// entity can be dropped on the indicated target entity.
/// No need to check range / reachability in here.
/// Returning true will cause the target entity to be highlighted as
/// a potential target and allow dropping when in range.
/// </summary>
/// <returns>
/// True if target is a valid target to be dropped on by this component's
/// entity, false otherwise.
/// </returns>
bool CanDrop(CanDropEventArgs args);
/// <summary>
/// Invoked when this component's entity is being dropped on another.
/// Other drag and drop interactions may be attempted if this one fails.
/// </summary>
/// <param name="args">
/// The information about the drag, such as who is doing it.
/// </param>
/// <returns>
/// True if an interaction occurred and no further interaction should
/// be processed for this drop, false otherwise.
/// </returns>
bool Drop(DragDropEventArgs args)
{
return false;
}
}
public class StartDragDropEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of <see cref="StartDragDropEventArgs"/>.
/// </summary>
/// <param name="user">The entity doing the drag and drop.</param>
/// <param name="dragged">The entity that is being dragged and dropped.</param>
public StartDragDropEventArgs(IEntity user, IEntity dragged)
{
User = user;
Dragged = dragged;
}
/// <summary>
/// The entity doing the drag and drop.
/// </summary>
public IEntity User { get; }
/// <summary>
/// The entity that is being dragged.
/// </summary>
public IEntity Dragged { get; }
}
public class CanDropEventArgs : StartDragDropEventArgs
{
/// <summary>
/// Creates a new instance of <see cref="CanDropEventArgs"/>.
/// </summary>
/// <param name="user">The entity doing the drag and drop.</param>
/// <param name="dragged">The entity that is being dragged and dropped.</param>
/// <param name="target">The entity that <see cref="dropped"/> is being dropped onto.</param>
public CanDropEventArgs(IEntity user, IEntity dragged, IEntity target) : base(user, dragged)
{
Target = target;
}
/// <summary>
/// The entity that <see cref="StartDragDropEventArgs.Dragged"/>
/// is being dropped onto.
/// </summary>
public IEntity Target { get; }
}
public class DragDropEventArgs : CanDropEventArgs
{
/// <summary>
/// Creates a new instance of <see cref="DragDropEventArgs"/>.
/// </summary>
/// <param name="user">The entity doing the drag and drop.</param>
/// <param name="dropLocation">The location where <see cref="dropped"/> is being dropped.</param>
/// <param name="dragged">The entity that is being dragged and dropped.</param>
/// <param name="target">The entity that <see cref="dropped"/> is being dropped onto.</param>
public DragDropEventArgs(IEntity user, EntityCoordinates dropLocation, IEntity dragged, IEntity target) : base(user, dragged, target)
{
DropLocation = dropLocation;
}
/// <summary>
/// The location where <see cref="StartDragDropEventArgs.Dragged"/>
/// is being dropped.
/// </summary>
public EntityCoordinates DropLocation { get; }
}
}