get that crap outta here (completely rewrites inventorysystem) (#5807)
* some work * equip: done unequip: todo * unequipping done & refactored events * workin * movin * reee namespaces * stun * mobstate * fixes * some work on events * removes serverside itemcomp & misc fixes * work * smol merge fix * ports template to prototype & finishes ui * moves relay & adds containerenumerator * actions & cuffs * my god what is actioncode * more fixes * im loosing my grasp on reality * more fixes * more work * explosions * yes * more work * more fixes * merge master & misc fixed because i forgot to commit before merging master * more fixes * fixes * moar * more work * moar fixes * suffixmap * more work on client * motivation low * no. no containers * mirroring client to server * fixes * move serverinvcomp * serverinventorycomponent is dead * gaming * only strippable & ai left... * only ai and richtext left * fixes ai * fixes * fixes sprite layers * more fixes * resolves optional * yes * stable™️ * fixes * moar fixes * moar * fix some tests * lmao * no comment * good to merge™️ * fixes build but for real * adresses some reviews * adresses some more reviews * nullables, yo * fixes lobbyscreen * timid refactor to differentiate actor & target * adresses more reviews * more * my god what a mess * removed the rest of duplicates * removed duplicate slotflags and renamed shoes to feet * removes another unused one * yes * fixes lobby & makes tryunequip return unequipped item * fixes * some funny renames * fixes * misc improvements to attemptevents * fixes * merge fixes Co-authored-by: Paul Ritter <ritter.paul1@gmail.com>
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Inventory.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Pops up a message when equipped / unequipped (including hands).
|
||||
/// For debugging purposes.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class DebugEquipComponent : Component, IEquipped, IEquippedHand, IUnequipped, IUnequippedHand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public override string Name => "DebugEquip";
|
||||
|
||||
void IEquipped.Equipped(EquippedEventArgs eventArgs)
|
||||
{
|
||||
eventArgs.User.PopupMessage("equipped " + _entMan.GetComponent<MetaDataComponent>(Owner).EntityName);
|
||||
}
|
||||
|
||||
void IEquippedHand.EquippedHand(EquippedHandEventArgs eventArgs)
|
||||
{
|
||||
eventArgs.User.PopupMessage("equipped hand " + _entMan.GetComponent<MetaDataComponent>(Owner).EntityName);
|
||||
}
|
||||
|
||||
void IUnequipped.Unequipped(UnequippedEventArgs eventArgs)
|
||||
{
|
||||
eventArgs.User.PopupMessage("unequipped " + _entMan.GetComponent<MetaDataComponent>(Owner).EntityName);
|
||||
}
|
||||
|
||||
void IUnequippedHand.UnequippedHand(UnequippedHandEventArgs eventArgs)
|
||||
{
|
||||
eventArgs.User.PopupMessage("unequipped hand" + _entMan.GetComponent<MetaDataComponent>(Owner).EntityName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Items;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using static Content.Shared.Inventory.EquipmentSlotDefines;
|
||||
|
||||
namespace Content.Server.Inventory.Components
|
||||
{
|
||||
// Handles the special behavior of pockets/ID card slot and their relation to uniforms.
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IInventoryController))]
|
||||
public class HumanInventoryControllerComponent : Component, IInventoryController
|
||||
{
|
||||
public override string Name => "HumanInventoryController";
|
||||
|
||||
private InventoryComponent _inventory = default!;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_inventory = Owner.EnsureComponent<InventoryComponent>();
|
||||
}
|
||||
|
||||
bool IInventoryController.CanEquip(Slots slot, EntityUid entity, bool flagsCheck, [NotNullWhen(false)] out string? reason)
|
||||
{
|
||||
var slotMask = SlotMasks[slot];
|
||||
reason = null;
|
||||
|
||||
if ((slotMask & (SlotFlags.POCKET | SlotFlags.IDCARD)) != SlotFlags.NONE)
|
||||
{
|
||||
// Can't wear stuff in ID card or pockets unless you have a uniform.
|
||||
if (_inventory.GetSlotItem(Slots.INNERCLOTHING) == null)
|
||||
{
|
||||
reason = Loc.GetString(slotMask == SlotFlags.IDCARD
|
||||
? "human-inventory-controller-component-need-uniform-to-store-in-id-slot-text"
|
||||
: "human-inventory-controller-component-need-uniform-to-store-in-pockets-text");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (slotMask == SlotFlags.POCKET)
|
||||
{
|
||||
var itemComponent = IoCManager.Resolve<IEntityManager>().GetComponent<ItemComponent>(entity);
|
||||
|
||||
// If this item is small enough then it always fits in pockets.
|
||||
if (itemComponent.Size <= (int) ReferenceSizes.Pocket)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (!flagsCheck)
|
||||
{
|
||||
reason = Loc.GetString("human-inventory-controller-component-too-large-text");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Standard flag check.
|
||||
return flagsCheck;
|
||||
}
|
||||
|
||||
public void CheckUniformExists() { Owner.SpawnTimer(0, DropIdAndPocketsIfWeNoLongerHaveAUniform); }
|
||||
|
||||
// Hey, it's descriptive.
|
||||
private void DropIdAndPocketsIfWeNoLongerHaveAUniform()
|
||||
{
|
||||
if (Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_inventory.GetSlotItem(Slots.INNERCLOTHING) != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void DropMaybe(Slots slot)
|
||||
{
|
||||
if (_inventory.GetSlotItem(slot) != null)
|
||||
{
|
||||
_inventory.Unequip(slot);
|
||||
}
|
||||
}
|
||||
|
||||
DropMaybe(Slots.POCKET1);
|
||||
DropMaybe(Slots.POCKET2);
|
||||
DropMaybe(Slots.IDCARD);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using static Content.Shared.Inventory.EquipmentSlotDefines;
|
||||
|
||||
namespace Content.Server.Inventory.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows for overriding inventory-related behavior on an entity.
|
||||
/// </summary>
|
||||
public interface IInventoryController
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be implemented to override "can this item be equipped" behavior.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to be equipped into.</param>
|
||||
/// <param name="entity">The entity to equip.</param>
|
||||
/// <param name="flagsCheck">Whether the entity passes default slot masks & flags checks.</param>
|
||||
/// <param name="reason">The translated reason why the item cannot be equiped, if this function returns false. Can be null.</param>
|
||||
/// <returns>True if the entity can be equipped, false otherwise</returns>
|
||||
bool CanEquip(Slots slot, EntityUid entity, bool flagsCheck, [NotNullWhen(false)] out string? reason)
|
||||
{
|
||||
reason = null;
|
||||
return flagsCheck;
|
||||
}
|
||||
|
||||
bool CanEquip(Slots slot, EntityUid entity, bool flagsCheck) => CanEquip(slot, entity, flagsCheck, out _);
|
||||
}
|
||||
}
|
||||
@@ -1,576 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Clothing.Components;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Acts;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Movement.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Content.Shared.Inventory.EquipmentSlotDefines;
|
||||
using static Content.Shared.Inventory.SharedInventoryComponent.ClientInventoryMessage;
|
||||
|
||||
namespace Content.Server.Inventory.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedInventoryComponent))]
|
||||
public class InventoryComponent : SharedInventoryComponent, IExAct
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
|
||||
[ViewVariables] private readonly Dictionary<Slots, ContainerSlot> _slotContainers = new();
|
||||
|
||||
private KeyValuePair<Slots, (EntityUid entity, bool fits)>? _hoverEntity;
|
||||
|
||||
public IEnumerable<Slots> Slots => _slotContainers.Keys;
|
||||
|
||||
public event Action? OnItemChanged;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
foreach (var slotName in InventoryInstance.SlotMasks)
|
||||
{
|
||||
if (slotName != EquipmentSlotDefines.Slots.NONE)
|
||||
{
|
||||
AddSlot(slotName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
{
|
||||
var slots = _slotContainers.Keys.ToList();
|
||||
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
if (TryGetSlotItem(slot, out ItemComponent? item))
|
||||
{
|
||||
_entities.DeleteEntity(item.Owner);
|
||||
}
|
||||
|
||||
RemoveSlot(slot);
|
||||
}
|
||||
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetAllHeldItems()
|
||||
{
|
||||
foreach (var (_, container) in _slotContainers)
|
||||
{
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
yield return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to get container name for specified slot on this component
|
||||
/// </summary>
|
||||
/// <param name="slot"></param>
|
||||
/// <returns></returns>
|
||||
private string GetSlotString(Slots slot)
|
||||
{
|
||||
return Name + "_" + Enum.GetName(typeof(Slots), slot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the clothing equipped to the specified slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to get the item for.</param>
|
||||
/// <returns>Null if the slot is empty, otherwise the item.</returns>
|
||||
public ItemComponent? GetSlotItem(Slots slot)
|
||||
{
|
||||
return GetSlotItem<ItemComponent>(slot);
|
||||
}
|
||||
|
||||
public IEnumerable<T?> LookupItems<T>() where T : Component
|
||||
{
|
||||
return _slotContainers.Values
|
||||
.SelectMany(x => x.ContainedEntities.Select(e => _entities.GetComponentOrNull<T>(e)))
|
||||
.Where(x => x != null);
|
||||
}
|
||||
|
||||
public T? GetSlotItem<T>(Slots slot) where T : ItemComponent
|
||||
{
|
||||
if (!_slotContainers.ContainsKey(slot))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var containedEntity = _slotContainers[slot].ContainedEntity;
|
||||
if (containedEntity != null && _entities.GetComponent<MetaDataComponent>(containedEntity.Value).EntityDeleted)
|
||||
{
|
||||
_slotContainers.Remove(slot);
|
||||
containedEntity = null;
|
||||
Dirty();
|
||||
}
|
||||
|
||||
return containedEntity.HasValue ? _entities.GetComponent<T>(containedEntity.Value) : null;
|
||||
}
|
||||
|
||||
public bool TryGetSlotItem<T>(Slots slot, [NotNullWhen(true)] out T? itemComponent) where T : ItemComponent
|
||||
{
|
||||
itemComponent = GetSlotItem<T>(slot);
|
||||
return itemComponent != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equips slothing to the specified slot.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will fail if there is already an item in the specified slot.
|
||||
/// </remarks>
|
||||
/// <param name="slot">The slot to put the item in.</param>
|
||||
/// <param name="item">The item to insert into the slot.</param>
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
/// <param name="reason">The translated reason why the item cannot be equipped, if this function returns false. Can be null.</param>
|
||||
/// <returns>True if the item was successfully inserted, false otherwise.</returns>
|
||||
public bool Equip(Slots slot, ItemComponent item, bool mobCheck, [NotNullWhen(false)] out string? reason)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item),
|
||||
"Clothing must be passed here. To remove some clothing from a slot, use Unequip()");
|
||||
}
|
||||
|
||||
if (!CanEquip(slot, item, mobCheck, out reason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inventorySlot = _slotContainers[slot];
|
||||
if (!inventorySlot.Insert(item.Owner))
|
||||
{
|
||||
reason = Loc.GetString("inventory-component-on-equip-cannot");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Make clothing component not inherit ItemComponent, for fuck's sake.
|
||||
// TODO: Make clothing component not required for playing a sound on equip... Move it to its own component.
|
||||
if (mobCheck && item is ClothingComponent { EquipSound: {} equipSound })
|
||||
{
|
||||
SoundSystem.Play(Filter.Pvs(Owner), equipSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
|
||||
_entitySystemManager.GetEntitySystem<InteractionSystem>().EquippedInteraction(Owner, item.Owner, slot);
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
Dirty();
|
||||
|
||||
UpdateMovementSpeed();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Equip(Slots slot, ItemComponent item, bool mobCheck = true) =>
|
||||
Equip(slot, item, mobCheck, out var _);
|
||||
|
||||
public bool Equip(Slots slot, EntityUid entity, bool mobCheck = true) =>
|
||||
Equip(slot, _entities.GetComponent<ItemComponent>(entity), mobCheck);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an item can be put in the specified slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to check for.</param>
|
||||
/// <param name="item">The item to check for.</param>
|
||||
/// <param name="reason">The translated reason why the item cannot be equiped, if this function returns false. Can be null.</param>
|
||||
/// <returns>True if the item can be inserted into the specified slot.</returns>
|
||||
public bool CanEquip(Slots slot, ItemComponent item, bool mobCheck, [NotNullWhen(false)] out string? reason)
|
||||
{
|
||||
var pass = false;
|
||||
reason = null;
|
||||
|
||||
if (mobCheck && !EntitySystem.Get<ActionBlockerSystem>().CanEquip(Owner))
|
||||
{
|
||||
reason = Loc.GetString("inventory-component-can-equip-cannot");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item is ClothingComponent clothing)
|
||||
{
|
||||
if (clothing.SlotFlags != SlotFlags.PREVENTEQUIP && (clothing.SlotFlags & SlotMasks[slot]) != 0)
|
||||
{
|
||||
pass = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = Loc.GetString("inventory-component-can-equip-does-not-fit");
|
||||
}
|
||||
}
|
||||
|
||||
if (_entities.TryGetComponent(Owner, out IInventoryController? controller))
|
||||
{
|
||||
pass = controller.CanEquip(slot, item.Owner, pass, out var controllerReason);
|
||||
reason = controllerReason ?? reason;
|
||||
}
|
||||
|
||||
if (!pass)
|
||||
{
|
||||
reason = reason ?? Loc.GetString("inventory-component-can-equip-cannot");
|
||||
return false;
|
||||
}
|
||||
|
||||
var canEquip = pass && _slotContainers[slot].CanInsert(item.Owner);
|
||||
|
||||
if (!canEquip)
|
||||
{
|
||||
reason = Loc.GetString("inventory-component-can-equip-cannot");
|
||||
}
|
||||
|
||||
return canEquip;
|
||||
}
|
||||
|
||||
public bool CanEquip(Slots slot, ItemComponent item, bool mobCheck = true) =>
|
||||
CanEquip(slot, item, mobCheck, out var _);
|
||||
|
||||
public bool CanEquip(Slots slot, EntityUid entity, bool mobCheck = true) =>
|
||||
CanEquip(slot, _entities.GetComponent<ItemComponent>(entity), mobCheck);
|
||||
|
||||
/// <summary>
|
||||
/// Drops the item in a slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to drop the item from.</param>
|
||||
/// <returns>True if an item was dropped, false otherwise.</returns>
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
public bool Unequip(Slots slot, bool mobCheck = true)
|
||||
{
|
||||
if (!CanUnequip(slot, mobCheck))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inventorySlot = _slotContainers[slot];
|
||||
|
||||
if (inventorySlot.ContainedEntity is not {Valid: true} entity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!inventorySlot.Remove(entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: The item should be dropped to the container our owner is in, if any.
|
||||
_entities.GetComponent<TransformComponent>(entity).AttachParentToContainerOrGrid();
|
||||
|
||||
_entitySystemManager.GetEntitySystem<InteractionSystem>().UnequippedInteraction(Owner, entity, slot);
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
Dirty();
|
||||
|
||||
UpdateMovementSpeed();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateMovementSpeed()
|
||||
{
|
||||
EntitySystem.Get<MovementSpeedModifierSystem>().RefreshMovementSpeedModifiers(Owner);
|
||||
}
|
||||
|
||||
public void ForceUnequip(Slots slot)
|
||||
{
|
||||
var inventorySlot = _slotContainers[slot];
|
||||
if (inventorySlot.ContainedEntity is not {Valid: true} entity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = _entities.GetComponent<ItemComponent>(entity);
|
||||
inventorySlot.ForceRemove(entity);
|
||||
|
||||
var itemTransform = _entities.GetComponent<TransformComponent>(entity);
|
||||
|
||||
itemTransform.AttachParentToContainerOrGrid();
|
||||
|
||||
_entitySystemManager.GetEntitySystem<InteractionSystem>().UnequippedInteraction(Owner, item.Owner, slot);
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an item can be dropped from the specified slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to check for.</param>
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
/// <returns>
|
||||
/// True if there is an item in the slot and it can be dropped, false otherwise.
|
||||
/// </returns>
|
||||
public bool CanUnequip(Slots slot, bool mobCheck = true)
|
||||
{
|
||||
if (mobCheck && !EntitySystem.Get<ActionBlockerSystem>().CanUnequip(Owner))
|
||||
return false;
|
||||
|
||||
var inventorySlot = _slotContainers[slot];
|
||||
return inventorySlot.ContainedEntity != null && inventorySlot.CanRemove(inventorySlot.ContainedEntity.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new slot to this inventory component.
|
||||
/// </summary>
|
||||
/// <param name="slot">The name of the slot to add.</param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown if the slot with specified name already exists.
|
||||
/// </exception>
|
||||
public ContainerSlot AddSlot(Slots slot)
|
||||
{
|
||||
if (HasSlot(slot))
|
||||
{
|
||||
throw new InvalidOperationException($"Slot '{slot}' already exists.");
|
||||
}
|
||||
|
||||
Dirty();
|
||||
|
||||
var container = ContainerHelpers.CreateContainer<ContainerSlot>(Owner, GetSlotString(slot));
|
||||
container.OccludesLight = false;
|
||||
_slotContainers[slot] = container;
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
return _slotContainers[slot];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a slot from this inventory component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the slot contains an item, the item is dropped.
|
||||
/// </remarks>
|
||||
/// <param name="slot">The name of the slot to remove.</param>
|
||||
public void RemoveSlot(Slots slot)
|
||||
{
|
||||
if (!HasSlot(slot))
|
||||
{
|
||||
throw new InvalidOperationException($"Slot '{slot}' does not exist.");
|
||||
}
|
||||
|
||||
ForceUnequip(slot);
|
||||
|
||||
var container = _slotContainers[slot];
|
||||
|
||||
container.Shutdown();
|
||||
_slotContainers.Remove(slot);
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a slot with the specified name exists.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot name to check.</param>
|
||||
/// <returns>True if the slot exists, false otherwise.</returns>
|
||||
public bool HasSlot(Slots slot)
|
||||
{
|
||||
return _slotContainers.ContainsKey(slot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The underlying Container System just notified us that an entity was removed from it.
|
||||
/// We need to make sure we process that removed entity as being unequipped from the slot.
|
||||
/// </summary>
|
||||
public void ForceUnequip(IContainer container, EntityUid entity)
|
||||
{
|
||||
// make sure this is one of our containers.
|
||||
// Technically the correct way would be to enumerate the possible slot names
|
||||
// comparing with this container, but I might as well put the dictionary to good use.
|
||||
if (container is not ContainerSlot slot || !_slotContainers.ContainsValue(slot))
|
||||
return;
|
||||
|
||||
if (_entities.TryGetComponent(entity, out ItemComponent? itemComp))
|
||||
{
|
||||
itemComp.RemovedFromSlot();
|
||||
}
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Message that tells us to equip or unequip items from the inventory slots
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
private async void HandleInventoryMessage(ClientInventoryMessage msg)
|
||||
{
|
||||
switch (msg.Updatetype)
|
||||
{
|
||||
case ClientInventoryUpdate.Equip:
|
||||
{
|
||||
var hands = _entities.GetComponent<HandsComponent>(Owner);
|
||||
var activeHand = hands.ActiveHand;
|
||||
var activeItem = hands.GetActiveHand;
|
||||
if (activeHand != null && activeItem != null && _entities.TryGetComponent(activeItem.Owner, out ItemComponent? item))
|
||||
{
|
||||
hands.TryDropNoInteraction();
|
||||
if (!Equip(msg.Inventoryslot, item, true, out var reason))
|
||||
{
|
||||
hands.PutInHand(item);
|
||||
Owner.PopupMessageCursor(reason);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ClientInventoryUpdate.Use:
|
||||
{
|
||||
var interactionSystem = _entitySystemManager.GetEntitySystem<InteractionSystem>();
|
||||
var hands = _entities.GetComponent<HandsComponent>(Owner);
|
||||
var activeHand = hands.GetActiveHand;
|
||||
var itemContainedInSlot = GetSlotItem(msg.Inventoryslot);
|
||||
if (itemContainedInSlot != null)
|
||||
{
|
||||
if (activeHand != null)
|
||||
{
|
||||
await interactionSystem.InteractUsing(Owner, activeHand.Owner, itemContainedInSlot.Owner,
|
||||
new EntityCoordinates());
|
||||
}
|
||||
else if (Unequip(msg.Inventoryslot))
|
||||
{
|
||||
hands.PutInHand(itemContainedInSlot);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ClientInventoryUpdate.Hover:
|
||||
{
|
||||
var hands = _entities.GetComponent<HandsComponent>(Owner);
|
||||
var activeHand = hands.GetActiveHand;
|
||||
if (activeHand != null && GetSlotItem(msg.Inventoryslot) == null)
|
||||
{
|
||||
var canEquip = CanEquip(msg.Inventoryslot, activeHand, true, out var reason);
|
||||
_hoverEntity =
|
||||
new KeyValuePair<Slots, (EntityUid entity, bool fits)>(msg.Inventoryslot,
|
||||
(Uid: activeHand.Owner, canEquip));
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel,
|
||||
ICommonSession? session = null)
|
||||
{
|
||||
base.HandleNetworkMessage(message, netChannel, session);
|
||||
|
||||
if (session == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(session));
|
||||
}
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case ClientInventoryMessage msg:
|
||||
var playerentity = session.AttachedEntity;
|
||||
|
||||
if (playerentity == Owner)
|
||||
HandleInventoryMessage(msg);
|
||||
break;
|
||||
|
||||
case OpenSlotStorageUIMessage msg:
|
||||
if (!HasSlot(msg.Slot)) // client input sanitization
|
||||
return;
|
||||
var item = GetSlotItem(msg.Slot);
|
||||
if (item != null && _entities.TryGetComponent(item.Owner, out ServerStorageComponent? storage))
|
||||
storage.OpenStorageUI(Owner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
var list = new List<KeyValuePair<Slots, EntityUid>>();
|
||||
foreach (var (slot, container) in _slotContainers)
|
||||
{
|
||||
if (container is {ContainedEntity: { }})
|
||||
{
|
||||
list.Add(new KeyValuePair<Slots, EntityUid>(slot, container.ContainedEntity.Value));
|
||||
}
|
||||
}
|
||||
|
||||
var hover = _hoverEntity;
|
||||
_hoverEntity = null;
|
||||
|
||||
return new InventoryComponentState(list, hover);
|
||||
}
|
||||
|
||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Severity < ExplosionSeverity.Heavy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var slot in _slotContainers.Values.ToList())
|
||||
{
|
||||
foreach (var entity in slot.ContainedEntities)
|
||||
{
|
||||
var exActs = _entities.GetComponents<IExAct>(entity).ToList();
|
||||
foreach (var exAct in exActs)
|
||||
{
|
||||
exAct.OnExplosion(eventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsEquipped(EntityUid item)
|
||||
{
|
||||
if (item == default) return false;
|
||||
foreach (var containerSlot in _slotContainers.Values)
|
||||
{
|
||||
// we don't want a recursive check here
|
||||
if (containerSlot.Contains(item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool TryGetSlot(Slots slot, [NotNullWhen(true)] out EntityUid? item)
|
||||
{
|
||||
if (_slotContainers.TryGetValue(slot, out var container))
|
||||
{
|
||||
item = container.ContainedEntity;
|
||||
return item != null;
|
||||
}
|
||||
|
||||
item = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using Content.Server.Inventory.Components;
|
||||
using Content.Server.Items;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.Inventory.EquipmentSlotDefines;
|
||||
|
||||
namespace Content.Server.Inventory
|
||||
{
|
||||
public static class InventoryHelpers
|
||||
{
|
||||
public static bool SpawnItemInSlot(this InventoryComponent inventory, Slots slot, string prototype, bool mobCheck = false)
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var protoManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var user = inventory.Owner;
|
||||
|
||||
// Let's do nothing if the owner of the inventory has been deleted.
|
||||
if (entityManager.Deleted(user))
|
||||
return false;
|
||||
|
||||
// If we don't have that slot or there's already an item there, we do nothing.
|
||||
if (!inventory.HasSlot(slot) || inventory.TryGetSlotItem(slot, out ItemComponent? _))
|
||||
return false;
|
||||
|
||||
// If the prototype in question doesn't exist, we do nothing.
|
||||
if (!protoManager.HasIndex<EntityPrototype>(prototype))
|
||||
return false;
|
||||
|
||||
// Let's spawn this first...
|
||||
var item = entityManager.SpawnEntity(prototype, entityManager.GetComponent<TransformComponent>(user).MapPosition);
|
||||
|
||||
// Helper method that deletes the item and returns false.
|
||||
bool DeleteItem()
|
||||
{
|
||||
entityManager.DeleteEntity(item);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this doesn't have an item component, then we can't do anything with it.
|
||||
if (!entityManager.TryGetComponent(item, out ItemComponent? itemComp))
|
||||
return DeleteItem();
|
||||
|
||||
// We finally try to equip the item, otherwise we delete it.
|
||||
return inventory.Equip(slot, itemComp, mobCheck) || DeleteItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Inventory.Components;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.Temperature.Systems;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Electrocution;
|
||||
using Content.Shared.Movement.EntitySystems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Inventory
|
||||
{
|
||||
class InventorySystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HumanInventoryControllerComponent, EntRemovedFromContainerMessage>(HandleRemovedFromContainer);
|
||||
SubscribeLocalEvent<InventoryComponent, EntRemovedFromContainerMessage>(HandleInvRemovedFromContainer);
|
||||
SubscribeLocalEvent<InventoryComponent, HighPressureEvent>(OnHighPressureEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, LowPressureEvent>(OnLowPressureEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, DamageModifyEvent>(OnDamageModify);
|
||||
SubscribeLocalEvent<InventoryComponent, ElectrocutionAttemptEvent>(OnElectrocutionAttempt);
|
||||
SubscribeLocalEvent<InventoryComponent, SlipAttemptEvent>(OnSlipAttemptEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
|
||||
SubscribeLocalEvent<InventoryComponent, ModifyChangedTemperatureEvent>(OnModifyTemperature);
|
||||
}
|
||||
|
||||
private void OnModifyTemperature(EntityUid uid, InventoryComponent component, ModifyChangedTemperatureEvent args)
|
||||
{
|
||||
RelayInventoryEvent(component, args);
|
||||
}
|
||||
|
||||
private void OnSlipAttemptEvent(EntityUid uid, InventoryComponent component, SlipAttemptEvent args)
|
||||
{
|
||||
if (component.TryGetSlotItem(EquipmentSlotDefines.Slots.SHOES, out ItemComponent? shoes))
|
||||
{
|
||||
RaiseLocalEvent(shoes.Owner, args, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRefreshMovespeed(EntityUid uid, InventoryComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
RelayInventoryEvent(component, args);
|
||||
}
|
||||
|
||||
private static void HandleInvRemovedFromContainer(EntityUid uid, InventoryComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
component.ForceUnequip(args.Container, args.Entity);
|
||||
}
|
||||
|
||||
private static void HandleRemovedFromContainer(EntityUid uid, HumanInventoryControllerComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
component.CheckUniformExists();
|
||||
}
|
||||
|
||||
private void OnHighPressureEvent(EntityUid uid, InventoryComponent component, HighPressureEvent args)
|
||||
{
|
||||
RelayInventoryEvent(component, args);
|
||||
}
|
||||
|
||||
private void OnLowPressureEvent(EntityUid uid, InventoryComponent component, LowPressureEvent args)
|
||||
{
|
||||
RelayInventoryEvent(component, args);
|
||||
}
|
||||
|
||||
private void OnElectrocutionAttempt(EntityUid uid, InventoryComponent component, ElectrocutionAttemptEvent args)
|
||||
{
|
||||
RelayInventoryEvent(component, args);
|
||||
}
|
||||
|
||||
private void OnDamageModify(EntityUid uid, InventoryComponent component, DamageModifyEvent args)
|
||||
{
|
||||
RelayInventoryEvent(component, args);
|
||||
}
|
||||
|
||||
private void RelayInventoryEvent<T>(InventoryComponent component, T args) where T : EntityEventArgs
|
||||
{
|
||||
foreach (var equipped in component.GetAllHeldItems())
|
||||
{
|
||||
RaiseLocalEvent(equipped, args, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Content.Server/Inventory/ServerInventoryComponent.cs
Normal file
8
Content.Server/Inventory/ServerInventoryComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Inventory;
|
||||
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(InventoryComponent))]
|
||||
public class ServerInventoryComponent : InventoryComponent { }
|
||||
70
Content.Server/Inventory/ServerInventorySystem.cs
Normal file
70
Content.Server/Inventory/ServerInventorySystem.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.Temperature.Systems;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
|
||||
|
||||
namespace Content.Server.Inventory
|
||||
{
|
||||
class ServerInventorySystem : InventorySystem
|
||||
{
|
||||
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<InventoryComponent, HighPressureEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, LowPressureEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, ModifyChangedTemperatureEvent>(RelayInventoryEvent);
|
||||
|
||||
SubscribeNetworkEvent<TryEquipNetworkMessage>(OnNetworkEquip);
|
||||
SubscribeNetworkEvent<TryUnequipNetworkMessage>(OnNetworkUnequip);
|
||||
SubscribeNetworkEvent<OpenSlotStorageNetworkMessage>(OnOpenSlotStorage);
|
||||
SubscribeNetworkEvent<UseSlotNetworkMessage>(OnUseSlot);
|
||||
}
|
||||
|
||||
private void OnUseSlot(UseSlotNetworkMessage ev)
|
||||
{
|
||||
if (!TryComp<HandsComponent>(ev.Uid, out var hands) || !TryGetSlotEntity(ev.Uid, ev.Slot, out var itemUid))
|
||||
return;
|
||||
|
||||
var activeHand = hands.GetActiveHand;
|
||||
if (activeHand != null)
|
||||
{
|
||||
_interactionSystem.InteractUsing(ev.Uid, activeHand.Owner, itemUid.Value,
|
||||
new EntityCoordinates());
|
||||
}
|
||||
else if (TryUnequip(ev.Uid, ev.Slot))
|
||||
{
|
||||
hands.PutInHand(itemUid.Value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void OnOpenSlotStorage(OpenSlotStorageNetworkMessage ev)
|
||||
{
|
||||
if (TryGetSlotEntity(ev.Uid, ev.Slot, out var entityUid) && TryComp<ServerStorageComponent>(entityUid, out var storageComponent))
|
||||
{
|
||||
storageComponent.OpenStorageUI(ev.Uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkUnequip(TryUnequipNetworkMessage ev)
|
||||
{
|
||||
TryUnequip(ev.Actor, ev.Target, ev.Slot, ev.Silent, ev.Force);
|
||||
}
|
||||
|
||||
private void OnNetworkEquip(TryEquipNetworkMessage ev)
|
||||
{
|
||||
TryEquip(ev.Actor, ev.Target, ev.ItemUid, ev.Slot, ev.Silent, ev.Force);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user