Rejigging Item slots (#4933)
* itemslot overhaul * remove "shared" prefix * handle component shutdown * comments, cleanup, tests * comments and minor tweak * rename ItemSlotManagerState * fix swapping * fix merge * Add ItemSlot verb text override * fix merge (IEntity -> entityUid) * Fix merge (LabelSystem) * Fix merge (nuke disk) * fix test
This commit is contained in:
@@ -42,7 +42,6 @@ namespace Content.IntegrationTests.Tests
|
||||
Slots:
|
||||
- idcard
|
||||
- type: PDA
|
||||
idCard: AssistantIDCard
|
||||
";
|
||||
[Test]
|
||||
public async Task SpawnItemInSlotTest()
|
||||
|
||||
@@ -29,13 +29,12 @@ namespace Content.IntegrationTests.Tests.PDA
|
||||
id: {PdaDummy}
|
||||
name: {PdaDummy}
|
||||
components:
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
pdaIdSlot:
|
||||
- type: PDA
|
||||
idSlot:
|
||||
name: ID Card
|
||||
whitelist:
|
||||
components:
|
||||
- IdCard
|
||||
- type: PDA
|
||||
- type: Item";
|
||||
|
||||
[Test]
|
||||
@@ -77,9 +76,9 @@ namespace Content.IntegrationTests.Tests.PDA
|
||||
var pdaComponent = dummyPda.GetComponent<PDAComponent>();
|
||||
var pdaIdCard = sEntityManager.SpawnEntity(IdCardDummy, player.Transform.MapPosition);
|
||||
|
||||
var itemSlots = dummyPda.GetComponent<SharedItemSlotsComponent>();
|
||||
sEntityManager.EntitySysManager.GetEntitySystem<SharedItemSlotsSystem>()
|
||||
.TryInsertContent(itemSlots, pdaIdCard, pdaComponent.IdSlot);
|
||||
var itemSlots = dummyPda.GetComponent<ItemSlotsComponent>();
|
||||
sEntityManager.EntitySysManager.GetEntitySystem<ItemSlotsSystem>()
|
||||
.TryInsert(dummyPda.Uid, pdaComponent.IdSlot, pdaIdCard);
|
||||
var pdaContainedId = pdaComponent.ContainedID;
|
||||
|
||||
// The PDA in the hand should be found first
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Content.IntegrationTests.Tests.Utility
|
||||
|
||||
// Test from serialized
|
||||
var dummy = entityManager.SpawnEntity("WhitelistDummy", mapCoordinates);
|
||||
var whitelistSer = dummy.GetComponent<SharedItemSlotsComponent>().Slots.Values.First().Whitelist;
|
||||
var whitelistSer = dummy.GetComponent<ItemSlotsComponent>().Slots.Values.First().Whitelist;
|
||||
Assert.That(whitelistSer, Is.Not.Null);
|
||||
|
||||
Assert.That(whitelistSer.Components, Is.Not.Null);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Sound;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -21,10 +22,11 @@ namespace Content.Server.Cabinet
|
||||
public SoundSpecifier DoorSound { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The slot name, used to get the actual item slot from the ItemSlotsComponent.
|
||||
/// The <see cref="ItemSlot"/> that stores the actual item. The entity whitelist, sounds, and other
|
||||
/// behaviours are specified by this <see cref="ItemSlot"/> definition.
|
||||
/// </summary>
|
||||
[DataField("cabinetSlot")]
|
||||
public string CabinetSlot = "cabinetSlot";
|
||||
public ItemSlot CabinetSlot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the cabinet is currently open or not.
|
||||
|
||||
@@ -4,54 +4,63 @@ using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Player;
|
||||
using System;
|
||||
|
||||
namespace Content.Server.Cabinet
|
||||
{
|
||||
public class ItemCabinetSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ItemCabinetComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ComponentRemove>(OnComponentRemove);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ComponentStartup>(OnComponentStartup);
|
||||
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ActivateInWorldEvent>(OnActivateInWorld);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ComponentStartup>(InitializeAppearance);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, GetActivationVerbsEvent>(AddToggleOpenVerb);
|
||||
|
||||
SubscribeLocalEvent<ItemCabinetComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, EntRemovedFromContainerMessage>(OnContainerModified);
|
||||
}
|
||||
|
||||
private void InitializeAppearance(EntityUid uid, ItemCabinetComponent component, ComponentStartup args)
|
||||
private void OnComponentInit(EntityUid uid, ItemCabinetComponent cabinet, ComponentInit args)
|
||||
{
|
||||
UpdateAppearance(uid, component);
|
||||
_itemSlotsSystem.AddItemSlot(uid, cabinet.Name, cabinet.CabinetSlot);
|
||||
}
|
||||
private void OnComponentRemove(EntityUid uid, ItemCabinetComponent cabinet, ComponentRemove args)
|
||||
{
|
||||
_itemSlotsSystem.RemoveItemSlot(uid, cabinet.CabinetSlot);
|
||||
}
|
||||
|
||||
private void OnComponentStartup(EntityUid uid, ItemCabinetComponent cabinet, ComponentStartup args)
|
||||
{
|
||||
UpdateAppearance(uid, cabinet);
|
||||
_itemSlotsSystem.SetLock(uid, cabinet.CabinetSlot.ID, !cabinet.Opened);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid,
|
||||
ItemCabinetComponent? cabinet = null,
|
||||
SharedItemSlotsComponent? itemSlots = null,
|
||||
SharedAppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref cabinet, ref itemSlots, ref appearance, false))
|
||||
if (!Resolve(uid, ref cabinet, ref appearance, false))
|
||||
return;
|
||||
|
||||
appearance.SetData(ItemCabinetVisuals.IsOpen, cabinet.Opened);
|
||||
|
||||
if (!itemSlots.Slots.TryGetValue(cabinet.CabinetSlot, out var slot))
|
||||
return;
|
||||
|
||||
appearance.SetData(ItemCabinetVisuals.ContainsItem, slot.HasEntity);
|
||||
appearance.SetData(ItemCabinetVisuals.ContainsItem, cabinet.CabinetSlot.HasItem);
|
||||
}
|
||||
|
||||
private void OnItemSlotChanged(EntityUid uid, ItemCabinetComponent cabinet, ItemSlotChangedEvent args)
|
||||
private void OnContainerModified(EntityUid uid, ItemCabinetComponent cabinet, ContainerModifiedMessage args)
|
||||
{
|
||||
UpdateAppearance(uid, cabinet, args.SlotsComponent);
|
||||
if (args.Container.ID == cabinet.CabinetSlot.ID)
|
||||
UpdateAppearance(uid, cabinet);
|
||||
}
|
||||
|
||||
private void AddToggleOpenVerb(EntityUid uid, ItemCabinetComponent cabinet, GetActivationVerbsEvent args)
|
||||
@@ -75,44 +84,6 @@ namespace Content.Server.Cabinet
|
||||
args.Verbs.Add(toggleVerb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try insert an item if the cabinet is opened. Otherwise, just try open it.
|
||||
/// </summary>
|
||||
private void OnInteractUsing(EntityUid uid, ItemCabinetComponent comp, InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!comp.Opened)
|
||||
ToggleItemCabinet(uid, comp);
|
||||
else
|
||||
_itemSlotsSystem.TryInsertContent(uid, args.Used, args.User);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the cabinet is opened and has an entity, try and take it. Otherwise toggle the cabinet open/closed;
|
||||
/// </summary>
|
||||
private void OnInteractHand(EntityUid uid, ItemCabinetComponent comp, InteractHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out SharedItemSlotsComponent itemSlots))
|
||||
return;
|
||||
|
||||
if (!itemSlots.Slots.TryGetValue(comp.CabinetSlot, out var slot))
|
||||
return;
|
||||
|
||||
if (comp.Opened && slot.HasEntity)
|
||||
_itemSlotsSystem.TryEjectContent(uid, comp.CabinetSlot, args.User);
|
||||
else
|
||||
ToggleItemCabinet(uid, comp);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnActivateInWorld(EntityUid uid, ItemCabinetComponent comp, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
@@ -132,6 +103,7 @@ namespace Content.Server.Cabinet
|
||||
|
||||
cabinet.Opened = !cabinet.Opened;
|
||||
SoundSystem.Play(Filter.Pvs(uid), cabinet.DoorSound.GetSound(), uid, AudioHelpers.WithVariation(0.15f));
|
||||
_itemSlotsSystem.SetLock(uid, cabinet.CabinetSlot.ID, !cabinet.Opened);
|
||||
|
||||
UpdateAppearance(uid, cabinet);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Content.Server.Cargo.Components
|
||||
{
|
||||
|
||||
//This entire class is a PLACEHOLDER for the cargo shuttle.
|
||||
//welp only need auto-docking now.
|
||||
|
||||
[RegisterComponent]
|
||||
public class CargoTelepadComponent : Component
|
||||
@@ -136,10 +137,9 @@ namespace Content.Server.Cargo.Components
|
||||
("approver", data.Approver)));
|
||||
|
||||
// attempt to attach the label
|
||||
if (_entityManager.TryGetComponent(product.Uid, out PaperLabelComponent label) &&
|
||||
_entityManager.TryGetComponent(product.Uid, out SharedItemSlotsComponent slots))
|
||||
if (_entityManager.TryGetComponent(product.Uid, out PaperLabelComponent label))
|
||||
{
|
||||
EntitySystem.Get<SharedItemSlotsSystem>().TryInsertContent(slots, printed, label.LabelSlot);
|
||||
EntitySystem.Get<ItemSlotsSystem>().TryInsert(OwnerUid, label.LabelSlot, printed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
@@ -12,6 +13,6 @@ namespace Content.Server.Labels.Components
|
||||
public override string Name => "PaperLabel";
|
||||
|
||||
[DataField("labelSlot")]
|
||||
public string LabelSlot = "labelSlot";
|
||||
public ItemSlot LabelSlot = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Labels;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
|
||||
namespace Content.Server.Labels
|
||||
{
|
||||
@@ -18,26 +18,35 @@ namespace Content.Server.Labels
|
||||
[UsedImplicitly]
|
||||
public class LabelSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LabelComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<PaperLabelComponent, ComponentInit>(InitializePaperLabel);
|
||||
SubscribeLocalEvent<PaperLabelComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
|
||||
SubscribeLocalEvent<PaperLabelComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<PaperLabelComponent, ComponentRemove>(OnComponentRemove);
|
||||
SubscribeLocalEvent<PaperLabelComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
|
||||
SubscribeLocalEvent<PaperLabelComponent, EntRemovedFromContainerMessage>(OnContainerModified);
|
||||
SubscribeLocalEvent<PaperLabelComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void InitializePaperLabel(EntityUid uid, PaperLabelComponent component, ComponentInit args)
|
||||
private void OnComponentInit(EntityUid uid, PaperLabelComponent component, ComponentInit args)
|
||||
{
|
||||
_itemSlotsSystem.AddItemSlot(uid, component.Name, component.LabelSlot);
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out SharedAppearanceComponent appearance))
|
||||
return;
|
||||
|
||||
appearance.SetData(PaperLabelVisuals.HasLabel, false);
|
||||
}
|
||||
|
||||
private void OnComponentRemove(EntityUid uid, PaperLabelComponent component, ComponentRemove args)
|
||||
{
|
||||
_itemSlotsSystem.RemoveItemSlot(uid, component.LabelSlot);
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, LabelComponent? label, ExaminedEvent args)
|
||||
{
|
||||
if (!Resolve(uid, ref label))
|
||||
@@ -53,12 +62,7 @@ namespace Content.Server.Labels
|
||||
|
||||
private void OnExamined(EntityUid uid, PaperLabelComponent comp, ExaminedEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out SharedItemSlotsComponent slots))
|
||||
return;
|
||||
|
||||
var label = _itemSlotsSystem.PeekItemInSlot(slots, comp.LabelSlot);
|
||||
|
||||
if (label == null)
|
||||
if (comp.LabelSlot.Item == null)
|
||||
return;
|
||||
|
||||
if (!args.IsInDetailsRange)
|
||||
@@ -67,8 +71,8 @@ namespace Content.Server.Labels
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent(label.Uid, out PaperComponent paper))
|
||||
// should never happen
|
||||
if (!EntityManager.TryGetComponent(comp.LabelSlot.Item.Uid, out PaperComponent paper))
|
||||
// Assuming yaml has the correct entity whitelist, this should not happen.
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(paper.Content))
|
||||
@@ -83,15 +87,15 @@ namespace Content.Server.Labels
|
||||
}
|
||||
|
||||
|
||||
private void OnItemSlotChanged(EntityUid uid, PaperLabelComponent component, ItemSlotChangedEvent args)
|
||||
private void OnContainerModified(EntityUid uid, PaperLabelComponent label, ContainerModifiedMessage args)
|
||||
{
|
||||
if (args.SlotName != component.LabelSlot)
|
||||
if (args.Container.ID != label.LabelSlot.ID)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out SharedAppearanceComponent appearance))
|
||||
return;
|
||||
|
||||
appearance.SetData(PaperLabelVisuals.HasLabel, args.ContainedItem != null);
|
||||
appearance.SetData(PaperLabelVisuals.HasLabel, label.LabelSlot.HasItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,12 @@ namespace Content.Server.Nuke
|
||||
public int Timer = 180;
|
||||
|
||||
/// <summary>
|
||||
/// Slot name for to store nuclear disk inside bomb.
|
||||
/// See <see cref="SharedItemSlotsComponent"/> for mor info.
|
||||
/// The <see cref="ItemSlot"/> that stores the nuclear disk. The entity whitelist, sounds, and some other
|
||||
/// behaviours are specified by this <see cref="ItemSlot"/> definition. Make sure the whitelist, is correct
|
||||
/// otherwise a blank bit of paper will work as a "disk".
|
||||
/// </summary>
|
||||
[DataField("slot")]
|
||||
public string DiskSlotName = "DiskSlot";
|
||||
[DataField("diskSlot")]
|
||||
public ItemSlot DiskSlot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Annihilation radius in which all human players will be gibed
|
||||
@@ -71,13 +72,6 @@ namespace Content.Server.Nuke
|
||||
[ViewVariables]
|
||||
public float RemainingTime;
|
||||
|
||||
/// <summary>
|
||||
/// Does bomb contains valid entity inside <see cref="DiskSlotName"/>?
|
||||
/// If it is, user can anchor bomb or enter nuclear code to arm it.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool DiskInserted = false;
|
||||
|
||||
/// <summary>
|
||||
/// Curent nuclear code buffer. Entered manually by players.
|
||||
/// If valid it will allow arm/disarm bomb.
|
||||
|
||||
@@ -19,6 +19,7 @@ using Content.Server.Coordinates.Helpers;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Sound;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Nuke
|
||||
{
|
||||
@@ -26,7 +27,7 @@ namespace Content.Server.Nuke
|
||||
{
|
||||
[Dependency] private readonly NukeCodeSystem _codes = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly SharedItemSlotsSystem _itemSlots = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
|
||||
[Dependency] private readonly PopupSystem _popups = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
@@ -39,7 +40,8 @@ namespace Content.Server.Nuke
|
||||
SubscribeLocalEvent<NukeComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<NukeComponent, ComponentRemove>(OnRemove);
|
||||
SubscribeLocalEvent<NukeComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<NukeComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
|
||||
SubscribeLocalEvent<NukeComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
|
||||
SubscribeLocalEvent<NukeComponent, EntRemovedFromContainerMessage>(OnItemSlotChanged);
|
||||
|
||||
// anchoring logic
|
||||
SubscribeLocalEvent<NukeComponent, AnchorAttemptEvent>(OnAnchorAttempt);
|
||||
@@ -59,6 +61,7 @@ namespace Content.Server.Nuke
|
||||
private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args)
|
||||
{
|
||||
component.RemainingTime = component.Timer;
|
||||
_itemSlots.AddItemSlot(uid, component.Name, component.DiskSlot);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -93,14 +96,14 @@ namespace Content.Server.Nuke
|
||||
private void OnRemove(EntityUid uid, NukeComponent component, ComponentRemove args)
|
||||
{
|
||||
_tickingBombs.Remove(uid);
|
||||
_itemSlots.RemoveItemSlot(uid, component.DiskSlot);
|
||||
}
|
||||
|
||||
private void OnItemSlotChanged(EntityUid uid, NukeComponent component, ItemSlotChangedEvent args)
|
||||
private void OnItemSlotChanged(EntityUid uid, NukeComponent component, ContainerModifiedMessage args)
|
||||
{
|
||||
if (args.SlotName != component.DiskSlotName)
|
||||
if (args.Container.ID != component.DiskSlot.ID)
|
||||
return;
|
||||
|
||||
component.DiskInserted = args.ContainedItem != null;
|
||||
UpdateStatus(uid, component);
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
@@ -137,7 +140,7 @@ namespace Content.Server.Nuke
|
||||
private void CheckAnchorAttempt(EntityUid uid, NukeComponent component, BaseAnchoredAttemptEvent args)
|
||||
{
|
||||
// cancel any anchor attempt without nuke disk
|
||||
if (!component.DiskInserted)
|
||||
if (!component.DiskSlot.HasItem)
|
||||
{
|
||||
var msg = Loc.GetString("nuke-component-cant-anchor");
|
||||
_popups.PopupEntity(msg, uid, Filter.Entities(args.User));
|
||||
@@ -160,15 +163,15 @@ namespace Content.Server.Nuke
|
||||
#region UI Events
|
||||
private void OnEjectButtonPressed(EntityUid uid, NukeComponent component, NukeEjectMessage args)
|
||||
{
|
||||
if (!component.DiskInserted)
|
||||
if (!component.DiskSlot.HasItem)
|
||||
return;
|
||||
|
||||
_itemSlots.TryEjectContent(uid, component.DiskSlotName, args.Session.AttachedEntity);
|
||||
_itemSlots.TryEjectToHands(uid, component.DiskSlot, args.Session.AttachedEntityUid);
|
||||
}
|
||||
|
||||
private async void OnAnchorButtonPressed(EntityUid uid, NukeComponent component, NukeAnchorMessage args)
|
||||
{
|
||||
if (!component.DiskInserted)
|
||||
if (!component.DiskSlot.HasItem)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out TransformComponent? transform))
|
||||
@@ -218,7 +221,7 @@ namespace Content.Server.Nuke
|
||||
|
||||
private void OnArmButtonPressed(EntityUid uid, NukeComponent component, NukeArmedMessage args)
|
||||
{
|
||||
if (!component.DiskInserted)
|
||||
if (!component.DiskSlot.HasItem)
|
||||
return;
|
||||
|
||||
if (component.Status == NukeStatus.AWAIT_ARM)
|
||||
@@ -240,12 +243,12 @@ namespace Content.Server.Nuke
|
||||
switch (component.Status)
|
||||
{
|
||||
case NukeStatus.AWAIT_DISK:
|
||||
if (component.DiskInserted)
|
||||
if (component.DiskSlot.HasItem)
|
||||
component.Status = NukeStatus.AWAIT_CODE;
|
||||
break;
|
||||
case NukeStatus.AWAIT_CODE:
|
||||
{
|
||||
if (!component.DiskInserted)
|
||||
if (!component.DiskSlot.HasItem)
|
||||
{
|
||||
component.Status = NukeStatus.AWAIT_DISK;
|
||||
component.EnteredCode = "";
|
||||
@@ -299,7 +302,7 @@ namespace Content.Server.Nuke
|
||||
if (EntityManager.TryGetComponent(uid, out TransformComponent transform))
|
||||
anchored = transform.Anchored;
|
||||
|
||||
var allowArm = component.DiskInserted &&
|
||||
var allowArm = component.DiskSlot.HasItem &&
|
||||
(component.Status == NukeStatus.AWAIT_ARM ||
|
||||
component.Status == NukeStatus.ARMED);
|
||||
|
||||
@@ -307,7 +310,7 @@ namespace Content.Server.Nuke
|
||||
{
|
||||
Status = component.Status,
|
||||
RemainingTime = (int) component.RemainingTime,
|
||||
DiskInserted = component.DiskInserted,
|
||||
DiskInserted = component.DiskSlot.HasItem,
|
||||
IsAnchored = anchored,
|
||||
AllowArm = allowArm,
|
||||
EnteredCodeLength = component.EnteredCode.Length,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Access.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.PDA
|
||||
@@ -13,15 +16,18 @@ namespace Content.Server.PDA
|
||||
public override string Name => "PDA";
|
||||
|
||||
[DataField("idSlot")]
|
||||
public string IdSlot = "pdaIdSlot";
|
||||
public ItemSlot IdSlot = new();
|
||||
|
||||
[DataField("penSlot")]
|
||||
public string PenSlot = "pdaPenSlot";
|
||||
public ItemSlot PenSlot = new();
|
||||
|
||||
[ViewVariables] [DataField("idCard")] public string? StartingIdCard;
|
||||
// Really this should just be using ItemSlot.StartingItem. However, seeing as we have so many different starting
|
||||
// PDA's and no nice way to inherit the other fields from the ItemSlot data definition, this makes the yaml much
|
||||
// nicer to read.
|
||||
[DataField("idCard", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? IdCard;
|
||||
|
||||
[ViewVariables] public IdCardComponent? ContainedID;
|
||||
[ViewVariables] public bool PenInserted;
|
||||
[ViewVariables] public bool FlashlightOn;
|
||||
|
||||
[ViewVariables] public string? OwnerName;
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.PDA;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
@@ -16,7 +17,7 @@ namespace Content.Server.PDA
|
||||
{
|
||||
public class PDASystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedItemSlotsSystem _slotsSystem = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly UplinkSystem _uplinkSystem = default!;
|
||||
[Dependency] private readonly UnpoweredFlashlightSystem _unpoweredFlashlight = default!;
|
||||
|
||||
@@ -25,10 +26,12 @@ namespace Content.Server.PDA
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PDAComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<PDAComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<PDAComponent, ComponentRemove>(OnComponentRemove);
|
||||
|
||||
SubscribeLocalEvent<PDAComponent, ActivateInWorldEvent>(OnActivateInWorld);
|
||||
SubscribeLocalEvent<PDAComponent, UseInHandEvent>(OnUse);
|
||||
SubscribeLocalEvent<PDAComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
|
||||
SubscribeLocalEvent<PDAComponent, EntInsertedIntoContainerMessage>(OnItemInserted);
|
||||
SubscribeLocalEvent<PDAComponent, EntRemovedFromContainerMessage>(OnItemRemoved);
|
||||
SubscribeLocalEvent<PDAComponent, LightToggleEvent>(OnLightToggle);
|
||||
|
||||
SubscribeLocalEvent<PDAComponent, UplinkInitEvent>(OnUplinkInit);
|
||||
@@ -41,19 +44,16 @@ namespace Content.Server.PDA
|
||||
if (ui != null)
|
||||
ui.OnReceiveMessage += (msg) => OnUIMessage(pda, msg);
|
||||
|
||||
UpdatePDAAppearance(pda);
|
||||
if (pda.IdCard != null)
|
||||
pda.IdSlot.StartingItem = pda.IdCard;
|
||||
_itemSlotsSystem.AddItemSlot(uid, $"{pda.Name}-id", pda.IdSlot);
|
||||
_itemSlotsSystem.AddItemSlot(uid, $"{pda.Name}-pen", pda.PenSlot);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, PDAComponent pda, MapInitEvent args)
|
||||
private void OnComponentRemove(EntityUid uid, PDAComponent pda, ComponentRemove args)
|
||||
{
|
||||
// try to place ID inside item slot
|
||||
if (!string.IsNullOrEmpty(pda.StartingIdCard))
|
||||
{
|
||||
// if pda prototype doesn't have slots, ID will drop down on ground
|
||||
var idCard = EntityManager.SpawnEntity(pda.StartingIdCard, pda.Owner.Transform.Coordinates);
|
||||
if (EntityManager.TryGetComponent(uid, out SharedItemSlotsComponent? itemSlots))
|
||||
_slotsSystem.TryInsertContent(itemSlots, idCard, pda.IdSlot);
|
||||
}
|
||||
_itemSlotsSystem.RemoveItemSlot(uid, pda.IdSlot);
|
||||
_itemSlotsSystem.RemoveItemSlot(uid, pda.PenSlot);
|
||||
}
|
||||
|
||||
private void OnUse(EntityUid uid, PDAComponent pda, UseInHandEvent args)
|
||||
@@ -70,22 +70,19 @@ namespace Content.Server.PDA
|
||||
args.Handled = OpenUI(pda, args.User);
|
||||
}
|
||||
|
||||
private void OnItemSlotChanged(EntityUid uid, PDAComponent pda, ItemSlotChangedEvent args)
|
||||
private void OnItemInserted(EntityUid uid, PDAComponent pda, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
// check if ID slot changed
|
||||
if (args.SlotName == pda.IdSlot)
|
||||
if (args.Container.ID == pda.IdSlot.ID)
|
||||
pda.ContainedID = args.Entity.GetComponentOrNull<IdCardComponent>();
|
||||
|
||||
UpdatePDAAppearance(pda);
|
||||
UpdatePDAUserInterface(pda);
|
||||
}
|
||||
|
||||
private void OnItemRemoved(EntityUid uid, PDAComponent pda, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
var item = args.ContainedItem;
|
||||
if (item == null || !EntityManager.TryGetComponent(item.Value, out IdCardComponent ? idCard))
|
||||
if (args.Container.ID == pda.IdSlot.ID)
|
||||
pda.ContainedID = null;
|
||||
else
|
||||
pda.ContainedID = idCard;
|
||||
}
|
||||
else if (args.SlotName == pda.PenSlot)
|
||||
{
|
||||
var item = args.ContainedItem;
|
||||
pda.PenInserted = item != null;
|
||||
}
|
||||
|
||||
UpdatePDAAppearance(pda);
|
||||
UpdatePDAUserInterface(pda);
|
||||
@@ -142,11 +139,15 @@ namespace Content.Server.PDA
|
||||
var hasUplink = pda.Owner.HasComponent<UplinkComponent>();
|
||||
|
||||
var ui = pda.Owner.GetUIOrNull(PDAUiKey.Key);
|
||||
ui?.SetState(new PDAUpdateState(pda.FlashlightOn, pda.PenInserted, ownerInfo, hasUplink));
|
||||
ui?.SetState(new PDAUpdateState(pda.FlashlightOn, pda.PenSlot.HasItem, ownerInfo, hasUplink));
|
||||
}
|
||||
|
||||
private void OnUIMessage(PDAComponent pda, ServerBoundUserInterfaceMessage msg)
|
||||
{
|
||||
// cast EntityUid? to EntityUid
|
||||
if (msg.Session.AttachedEntityUid is not EntityUid playerUid)
|
||||
return;
|
||||
|
||||
switch (msg.Message)
|
||||
{
|
||||
case PDARequestUpdateInterfaceMessage _:
|
||||
@@ -161,12 +162,12 @@ namespace Content.Server.PDA
|
||||
|
||||
case PDAEjectIDMessage _:
|
||||
{
|
||||
_slotsSystem.TryEjectContent(pda.Owner.Uid, pda.IdSlot, msg.Session.AttachedEntity);
|
||||
_itemSlotsSystem.TryEjectToHands(pda.Owner.Uid, pda.IdSlot, playerUid);
|
||||
break;
|
||||
}
|
||||
case PDAEjectPenMessage _:
|
||||
{
|
||||
_slotsSystem.TryEjectContent(pda.Owner.Uid, pda.PenSlot, msg.Session.AttachedEntity);
|
||||
_itemSlotsSystem.TryEjectToHands(pda.Owner.Uid, pda.PenSlot, playerUid);
|
||||
break;
|
||||
}
|
||||
case PDAShowUplinkMessage _:
|
||||
|
||||
@@ -136,10 +136,10 @@ namespace Content.Server.Sandbox
|
||||
if (pda.ContainedID == null)
|
||||
{
|
||||
var newID = CreateFreshId();
|
||||
if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots))
|
||||
if (pda.Owner.TryGetComponent(out ItemSlotsComponent? itemSlots))
|
||||
{
|
||||
_entityManager.EntitySysManager.GetEntitySystem<SharedItemSlotsSystem>().
|
||||
TryInsertContent(itemSlots, newID, pda.IdSlot);
|
||||
_entityManager.EntitySysManager.GetEntitySystem<ItemSlotsSystem>().
|
||||
TryInsert(wornItem.Owner.Uid, pda.IdSlot, newID);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.Containers.ItemSlots
|
||||
{
|
||||
/// <summary>
|
||||
/// Item was placed in or removed from one of the slots in <see cref="SharedItemSlotsComponent"/>
|
||||
/// </summary>
|
||||
public class ItemSlotChangedEvent : EntityEventArgs
|
||||
{
|
||||
public SharedItemSlotsComponent SlotsComponent;
|
||||
public string SlotName;
|
||||
public ItemSlot Slot;
|
||||
public readonly EntityUid? ContainedItem;
|
||||
|
||||
public ItemSlotChangedEvent(SharedItemSlotsComponent slotsComponent, string slotName, ItemSlot slot)
|
||||
{
|
||||
SlotsComponent = slotsComponent;
|
||||
SlotName = slotName;
|
||||
Slot = slot;
|
||||
ContainedItem = slot.ContainerSlot.ContainedEntity?.Uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
124
Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs
Normal file
124
Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Shared.Containers.ItemSlots
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for entities that can hold items in different slots. Needed by ItemSlotSystem to support basic
|
||||
/// insert/eject interactions.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Friend(typeof(ItemSlotsSystem))]
|
||||
public class ItemSlotsComponent : Component
|
||||
{
|
||||
public override string Name => "ItemSlots";
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("slots")]
|
||||
public Dictionary<string, ItemSlot> Slots = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ItemSlotsComponentState : ComponentState
|
||||
{
|
||||
public readonly Dictionary<string, bool> SlotLocked;
|
||||
|
||||
public ItemSlotsComponentState(Dictionary<string, ItemSlot> slots)
|
||||
{
|
||||
SlotLocked = new(slots.Count);
|
||||
|
||||
foreach (var (key, slot) in slots)
|
||||
{
|
||||
SlotLocked[key] = slot.Locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is effectively a wrapper for a ContainerSlot that adds content functionality like entity whitelists and
|
||||
/// insert/eject sounds.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
[Friend(typeof(ItemSlotsSystem))]
|
||||
public class ItemSlot
|
||||
{
|
||||
[DataField("whitelist")]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
[DataField("insertSound")]
|
||||
public SoundSpecifier? InsertSound;
|
||||
// maybe default to /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg ??
|
||||
|
||||
[DataField("ejectSound")]
|
||||
public SoundSpecifier? EjectSound;
|
||||
// maybe default to /Audio/Machines/id_swipe.ogg?
|
||||
|
||||
/// <summary>
|
||||
/// The name of this item slot. This will be shown to the user in the verb menu.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will be passed through Loc.GetString. If the name is an empty string, then verbs will use the name
|
||||
/// of the currently held or currently inserted entity instead.
|
||||
/// </remarks>
|
||||
[DataField("name")]
|
||||
public string Name = string.Empty;
|
||||
|
||||
[DataField("startingItem", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? StartingItem;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not an item can currently be ejected or inserted from this slot.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This doesn't have to mean the slot is somehow physically locked. In the case of the item cabinet, the
|
||||
/// cabinet may simply be closed at the moment and needs to be opened first.
|
||||
/// </remarks>
|
||||
[DataField("locked")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Locked = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the item slots system will attempt to eject this item to the user's hands when interacted with.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For most item slots, this is probably not the case (eject is usually an alt-click interaction). But
|
||||
/// there are some exceptions. For example item cabinets and charging stations should probably eject their
|
||||
/// contents when clicked on normally.
|
||||
/// </remarks>
|
||||
[DataField("ejectOnInteract")]
|
||||
public bool EjectOnInteract = false;
|
||||
|
||||
/// <summary>
|
||||
/// Override the insert verb text. Defaults to [insert category] -> [item-name]. If not null, the verb will
|
||||
/// not be given a category.
|
||||
/// </summary>
|
||||
[DataField("insertVerbText")]
|
||||
public string? InsertVerbText;
|
||||
|
||||
/// <summary>
|
||||
/// Override the insert verb text. Defaults to [eject category] -> [item-name]. If not null, the verb will
|
||||
/// not be given a category.
|
||||
/// </summary>
|
||||
[DataField("ejectVerbText")]
|
||||
public string? EjectVerbText;
|
||||
|
||||
[ViewVariables]
|
||||
public ContainerSlot ContainerSlot = default!;
|
||||
|
||||
public string ID => ContainerSlot.ID;
|
||||
|
||||
// Convenience properties
|
||||
public bool HasItem => ContainerSlot.ContainedEntity != null;
|
||||
public IEntity? Item => ContainerSlot.ContainedEntity;
|
||||
}
|
||||
}
|
||||
455
Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs
Normal file
455
Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs
Normal file
@@ -0,0 +1,455 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.Containers.ItemSlots
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that handles interactions related to inserting/ejecting items into/from an item slot.
|
||||
/// </summary>
|
||||
public class ItemSlotsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ItemSlotsComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<ItemSlotsComponent, ComponentInit>(Oninitialize);
|
||||
|
||||
SubscribeLocalEvent<ItemSlotsComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<ItemSlotsComponent, InteractHandEvent>(OnInteractHand);
|
||||
|
||||
SubscribeLocalEvent<ItemSlotsComponent, GetAlternativeVerbsEvent>(AddEjectVerbs);
|
||||
SubscribeLocalEvent<ItemSlotsComponent, GetInteractionVerbsEvent>(AddInteractionVerbsVerbs);
|
||||
|
||||
SubscribeLocalEvent<ItemSlotsComponent, ComponentGetState>(GetItemSlotsState);
|
||||
SubscribeLocalEvent<ItemSlotsComponent, ComponentHandleState>(HandleItemSlotsState);
|
||||
}
|
||||
|
||||
#region ComponentManagement
|
||||
/// <summary>
|
||||
/// Spawn in starting items for any item slots that should have one.
|
||||
/// </summary>
|
||||
private void OnStartup(EntityUid uid, ItemSlotsComponent itemSlots, ComponentStartup args)
|
||||
{
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
if (slot.HasItem || string.IsNullOrEmpty(slot.StartingItem))
|
||||
continue;
|
||||
|
||||
var item = EntityManager.SpawnEntity(slot.StartingItem, itemSlots.Owner.Transform.Coordinates);
|
||||
slot.ContainerSlot.Insert(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure item slots have containers.
|
||||
/// </summary>
|
||||
private void Oninitialize(EntityUid uid, ItemSlotsComponent itemSlots, ComponentInit args)
|
||||
{
|
||||
foreach (var (id, slot) in itemSlots.Slots)
|
||||
{
|
||||
slot.ContainerSlot = ContainerHelpers.EnsureContainer<ContainerSlot>(itemSlots.Owner, id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a new item slot, store it in the <see cref="ItemSlotsComponent"/> and ensure the slot has an item
|
||||
/// container.
|
||||
/// </summary>
|
||||
public void AddItemSlot(EntityUid uid, string id, ItemSlot slot)
|
||||
{
|
||||
var itemSlots = EntityManager.EnsureComponent<ItemSlotsComponent>(uid);
|
||||
slot.ContainerSlot = ContainerHelpers.EnsureContainer<ContainerSlot>(itemSlots.Owner, id);
|
||||
DebugTools.Assert(!itemSlots.Slots.ContainsKey(id));
|
||||
itemSlots.Slots[id] = slot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove an item slot. This should generally be called whenever a component that added a slot is being
|
||||
/// removed.
|
||||
/// </summary>
|
||||
public void RemoveItemSlot(EntityUid uid, ItemSlot slot, ItemSlotsComponent? itemSlots = null)
|
||||
{
|
||||
slot.ContainerSlot.Shutdown();
|
||||
|
||||
// Don't log missing resolves. when an entity has all of its components removed, the ItemSlotsComponent may
|
||||
// have been removed before some other component that added an item slot (and is now trying to remove it).
|
||||
if (!Resolve(uid, ref itemSlots, logMissing: false))
|
||||
return;
|
||||
|
||||
itemSlots.Slots.Remove(slot.ID);
|
||||
|
||||
if (itemSlots.Slots.Count == 0)
|
||||
EntityManager.RemoveComponent(uid, itemSlots);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Interactions
|
||||
/// <summary>
|
||||
/// Attempt to take an item from a slot, if any are set to EjectOnInteract.
|
||||
/// </summary>
|
||||
private void OnInteractHand(EntityUid uid, ItemSlotsComponent itemSlots, InteractHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
if (slot.Locked || !slot.EjectOnInteract || slot.Item == null)
|
||||
continue;
|
||||
|
||||
args.Handled = true;
|
||||
TryEjectToHands(uid, slot, args.UserUid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to insert a held item in any fitting item slot. If a valid slot already contains an item, it will
|
||||
/// swap it out and place the old one in the user's hand.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This only handles the event if the user has an applicable entity that can be inserted. This allows for
|
||||
/// other interactions to still happen (e.g., open UI, or toggle-open), despite the user holding an item.
|
||||
/// Maybe this is undesirable.
|
||||
/// </remarks>
|
||||
private void OnInteractUsing(EntityUid uid, ItemSlotsComponent itemSlots, InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(args.UserUid, out SharedHandsComponent? hands))
|
||||
return;
|
||||
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
if (!CanInsert(args.UsedUid, slot, swap: true))
|
||||
continue;
|
||||
|
||||
// Drop the held item onto the floor. Return if the user cannot drop.
|
||||
if (!hands.Drop(args.Used))
|
||||
return;
|
||||
|
||||
if (slot.Item != null)
|
||||
hands.TryPutInAnyHand(slot.Item);
|
||||
|
||||
Insert(uid, slot, args.Used);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Insert
|
||||
/// <summary>
|
||||
/// Insert an item into a slot. This does not perform checks, so make sure to also use <see
|
||||
/// cref="CanInsert"/> or just use <see cref="TryInsert"/> instead.
|
||||
/// </summary>
|
||||
private void Insert(EntityUid uid, ItemSlot slot, IEntity item)
|
||||
{
|
||||
slot.ContainerSlot.Insert(item);
|
||||
// ContainerSlot automatically raises a directed EntInsertedIntoContainerMessage
|
||||
|
||||
if (slot.InsertSound != null)
|
||||
SoundSystem.Play(Filter.Pvs(uid), slot.InsertSound.GetSound(), uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether a given item can be inserted into a slot. Unless otherwise specified, this will return
|
||||
/// false if the slot is already filled.
|
||||
/// </summary>
|
||||
public bool CanInsert(EntityUid uid, ItemSlot slot, bool swap = false)
|
||||
{
|
||||
if (slot.Locked)
|
||||
return false;
|
||||
|
||||
if (!swap && slot.HasItem)
|
||||
return false;
|
||||
|
||||
if (slot.Whitelist != null && !slot.Whitelist.IsValid(uid))
|
||||
return false;
|
||||
|
||||
// We should also check ContainerSlot.CanInsert, but that prevents swapping interactions. Given that
|
||||
// ContainerSlot.CanInsert gets called when the item is actually inserted anyways, we can just get away with
|
||||
// fudging CanInsert and not performing those checks.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to insert item into a specific slot.
|
||||
/// </summary>
|
||||
/// <returns>False if failed to insert item</returns>
|
||||
public bool TryInsert(EntityUid uid, string id, IEntity item, ItemSlotsComponent? itemSlots = null)
|
||||
{
|
||||
if (!Resolve(uid, ref itemSlots))
|
||||
return false;
|
||||
|
||||
if (!itemSlots.Slots.TryGetValue(id, out var slot))
|
||||
return false;
|
||||
|
||||
return TryInsert(uid, slot, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to insert item into a specific slot.
|
||||
/// </summary>
|
||||
/// <returns>False if failed to insert item</returns>
|
||||
public bool TryInsert(EntityUid uid, ItemSlot slot, IEntity item)
|
||||
{
|
||||
if (!CanInsert(item.Uid, slot))
|
||||
return false;
|
||||
|
||||
Insert(uid, slot, item);
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Eject
|
||||
/// <summary>
|
||||
/// Eject an item into a slot. This does not perform checks (e.g., is the slot locked?), so you should
|
||||
/// probably just use <see cref="TryEject"/> instead.
|
||||
/// </summary>
|
||||
private void Eject(EntityUid uid, ItemSlot slot, IEntity item)
|
||||
{
|
||||
slot.ContainerSlot.Remove(item);
|
||||
// ContainerSlot automatically raises a directed EntRemovedFromContainerMessage
|
||||
|
||||
if (slot.EjectSound != null)
|
||||
SoundSystem.Play(Filter.Pvs(uid), slot.EjectSound.GetSound(), uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to eject an item from a slot.
|
||||
/// </summary>
|
||||
/// <returns>False if item slot is locked or has no item inserted</returns>
|
||||
public bool TryEject(EntityUid uid, ItemSlot slot, [NotNullWhen(true)] out IEntity? item)
|
||||
{
|
||||
item = null;
|
||||
|
||||
if (slot.Locked || slot.Item == null)
|
||||
return false;
|
||||
|
||||
item = slot.Item;
|
||||
Eject(uid, slot, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to eject item from a slot.
|
||||
/// </summary>
|
||||
/// <returns>False if the id is not valid, the item slot is locked, or it has no item inserted</returns>
|
||||
public bool TryEject(EntityUid uid, string id, [NotNullWhen(true)] out IEntity? item, ItemSlotsComponent? itemSlots = null)
|
||||
{
|
||||
item = null;
|
||||
|
||||
if (!Resolve(uid, ref itemSlots))
|
||||
return false;
|
||||
|
||||
if (!itemSlots.Slots.TryGetValue(id, out var slot))
|
||||
return false;
|
||||
|
||||
return TryEject(uid, slot, out item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to eject item from a slot directly into a user's hands. If they have no hands, the item will still
|
||||
/// be ejected onto the floor.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// False if the id is not valid, the item slot is locked, or it has no item inserted. True otherwise, even
|
||||
/// if the user has no hands.
|
||||
/// </returns>
|
||||
public bool TryEjectToHands(EntityUid uid, ItemSlot slot, EntityUid? user)
|
||||
{
|
||||
if (!TryEject(uid, slot, out var item))
|
||||
return false;
|
||||
|
||||
if (user != null && EntityManager.TryGetComponent(user.Value, out SharedHandsComponent? hands))
|
||||
hands.TryPutInAnyHand(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Verbs
|
||||
private void AddEjectVerbs(EntityUid uid, ItemSlotsComponent itemSlots, GetAlternativeVerbsEvent args)
|
||||
{
|
||||
if (args.Hands == null || !args.CanAccess ||!args.CanInteract ||
|
||||
!_actionBlockerSystem.CanPickup(args.User.Uid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
if (slot.Locked || !slot.HasItem)
|
||||
continue;
|
||||
|
||||
if (slot.EjectOnInteract)
|
||||
// For this item slot, ejecting/inserting is a primary interaction. Instead of an eject category
|
||||
// alt-click verb, there will be a "Take item" primary interaction verb.
|
||||
continue;
|
||||
|
||||
var verbSubject = slot.Name != string.Empty
|
||||
? Loc.GetString(slot.Name)
|
||||
: slot.Item!.Name ?? string.Empty;
|
||||
|
||||
Verb verb = new();
|
||||
verb.Act = () => TryEjectToHands(uid, slot, args.User.Uid);
|
||||
|
||||
if (slot.EjectVerbText == null)
|
||||
{
|
||||
verb.Text = verbSubject;
|
||||
verb.Category = VerbCategory.Eject;
|
||||
}
|
||||
else
|
||||
{
|
||||
verb.Text = Loc.GetString(slot.EjectVerbText);
|
||||
verb.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png";
|
||||
}
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInteractionVerbsVerbs(EntityUid uid, ItemSlotsComponent itemSlots, GetInteractionVerbsEvent args)
|
||||
{
|
||||
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
// If there are any slots that eject on left-click, add a "Take <item>" verb.
|
||||
if (_actionBlockerSystem.CanPickup(args.User.Uid))
|
||||
{
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
if (!slot.EjectOnInteract || slot.Locked || !slot.HasItem)
|
||||
continue;
|
||||
|
||||
var verbSubject = slot.Name != string.Empty
|
||||
? Loc.GetString(slot.Name)
|
||||
: slot.Item!.Name ?? string.Empty;
|
||||
|
||||
Verb takeVerb = new();
|
||||
takeVerb.Act = () => TryEjectToHands(uid, slot, args.User.Uid);
|
||||
takeVerb.IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png";
|
||||
|
||||
if (slot.EjectVerbText == null)
|
||||
takeVerb.Text = Loc.GetString("take-item-verb-text", ("subject", verbSubject));
|
||||
else
|
||||
takeVerb.Text = Loc.GetString(slot.EjectVerbText);
|
||||
|
||||
args.Verbs.Add(takeVerb);
|
||||
}
|
||||
}
|
||||
|
||||
// Next, add the insert-item verbs
|
||||
if (args.Using == null || !_actionBlockerSystem.CanDrop(args.User.Uid))
|
||||
return;
|
||||
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
if (!CanInsert(args.Using.Uid, slot))
|
||||
continue;
|
||||
|
||||
var verbSubject = slot.Name != string.Empty
|
||||
? Loc.GetString(slot.Name)
|
||||
: args.Using.Name ?? string.Empty;
|
||||
|
||||
Verb insertVerb = new();
|
||||
insertVerb.Act = () => Insert(uid, slot, args.Using);
|
||||
|
||||
if (slot.InsertVerbText != null)
|
||||
{
|
||||
insertVerb.Text = Loc.GetString(slot.InsertVerbText);
|
||||
insertVerb.IconTexture = "/Textures/Interface/VerbIcons/insert.svg.192dpi.png";
|
||||
}
|
||||
else if(slot.EjectOnInteract)
|
||||
{
|
||||
// Inserting/ejecting is a primary interaction for this entity. Instead of using the insert
|
||||
// category, we will use a single "Place <item>" verb.
|
||||
insertVerb.Text = Loc.GetString("place-item-verb-text", ("subject", verbSubject));
|
||||
insertVerb.IconTexture = "/Textures/Interface/VerbIcons/drop.svg.192dpi.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
insertVerb.Category = VerbCategory.Insert;
|
||||
insertVerb.Text = verbSubject;
|
||||
}
|
||||
|
||||
args.Verbs.Add(insertVerb);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Get the contents of some item slot.
|
||||
/// </summary>
|
||||
public IEntity? GetItem(EntityUid uid, string id, ItemSlotsComponent? itemSlots = null)
|
||||
{
|
||||
if (!Resolve(uid, ref itemSlots))
|
||||
return null;
|
||||
|
||||
return itemSlots.Slots.GetValueOrDefault(id)?.Item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lock an item slot. This stops items from being inserted into or ejected from this slot.
|
||||
/// </summary>
|
||||
public void SetLock(EntityUid uid, string id, bool locked, ItemSlotsComponent? itemSlots = null)
|
||||
{
|
||||
if (!Resolve(uid, ref itemSlots))
|
||||
return;
|
||||
|
||||
if (!itemSlots.Slots.TryGetValue(id, out var slot))
|
||||
return;
|
||||
|
||||
SetLock(itemSlots, slot, locked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lock an item slot. This stops items from being inserted into or ejected from this slot.
|
||||
/// </summary>
|
||||
public void SetLock(ItemSlotsComponent itemSlots, ItemSlot slot, bool locked)
|
||||
{
|
||||
slot.Locked = locked;
|
||||
itemSlots.Dirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the locked state of the managed item slots.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that the slot's ContainerSlot performs its own networking, so we don't need to send information
|
||||
/// about the contained entity.
|
||||
/// </remarks>
|
||||
private void HandleItemSlotsState(EntityUid uid, ItemSlotsComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ItemSlotsComponentState state)
|
||||
return;
|
||||
|
||||
foreach (var (id, locked) in state.SlotLocked)
|
||||
{
|
||||
component.Slots[id].Locked = locked;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetItemSlotsState(EntityUid uid, ItemSlotsComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new ItemSlotsComponentState(component.Slots);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Shared.Containers.ItemSlots
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for entities that can hold items in different slots
|
||||
/// Allows basic insert/eject interaction
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class SharedItemSlotsComponent : Component
|
||||
{
|
||||
public override string Name => "ItemSlots";
|
||||
|
||||
[ViewVariables] [DataField("slots")] public Dictionary<string, ItemSlot> Slots = new();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[DataDefinition]
|
||||
public class ItemSlot
|
||||
{
|
||||
[ViewVariables] [DataField("whitelist")] public EntityWhitelist? Whitelist;
|
||||
[ViewVariables] [DataField("insertSound")] public SoundSpecifier? InsertSound;
|
||||
[ViewVariables] [DataField("ejectSound")] public SoundSpecifier? EjectSound;
|
||||
|
||||
/// <summary>
|
||||
/// The name of this item slot. This will be shown to the user in the verb menu.
|
||||
/// </summary>
|
||||
[ViewVariables] public string Name
|
||||
{
|
||||
get => _name != string.Empty
|
||||
? Loc.GetString(_name)
|
||||
: ContainerSlot.ContainedEntity?.Name ?? string.Empty;
|
||||
set => _name = value;
|
||||
}
|
||||
[DataField("name")] private string _name = string.Empty;
|
||||
|
||||
[DataField("item", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
[ViewVariables] public string? StartingItem;
|
||||
|
||||
[ViewVariables] public ContainerSlot ContainerSlot = default!;
|
||||
|
||||
public bool HasEntity => ContainerSlot.ContainedEntity != null;
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Shared.Containers.ItemSlots
|
||||
{
|
||||
public class SharedItemSlotsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharedItemSlotsComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<SharedItemSlotsComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SharedItemSlotsComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
|
||||
SubscribeLocalEvent<SharedItemSlotsComponent, GetAlternativeVerbsEvent>(AddEjectVerbs);
|
||||
SubscribeLocalEvent<SharedItemSlotsComponent, GetInteractionVerbsEvent>(AddInsertVerbs);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, SharedItemSlotsComponent itemSlots, ComponentInit args)
|
||||
{
|
||||
// create container for each slot
|
||||
foreach (var pair in itemSlots.Slots)
|
||||
{
|
||||
var slotName = pair.Key;
|
||||
var slot = pair.Value;
|
||||
|
||||
slot.ContainerSlot = ContainerHelpers.EnsureContainer<ContainerSlot>(itemSlots.Owner, slotName);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SharedItemSlotsComponent itemSlots, MapInitEvent args)
|
||||
{
|
||||
foreach (var pair in itemSlots.Slots)
|
||||
{
|
||||
var slot = pair.Value;
|
||||
var slotName = pair.Key;
|
||||
|
||||
// Check if someone already put item inside container
|
||||
if (slot.ContainerSlot.ContainedEntity != null)
|
||||
continue;
|
||||
|
||||
// Try to spawn item inside each slot
|
||||
if (!string.IsNullOrEmpty(slot.StartingItem))
|
||||
{
|
||||
var item = EntityManager.SpawnEntity(slot.StartingItem, itemSlots.Owner.Transform.Coordinates);
|
||||
slot.ContainerSlot.Insert(item);
|
||||
|
||||
RaiseLocalEvent(uid, new ItemSlotChangedEvent(itemSlots, slotName, slot));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddEjectVerbs(EntityUid uid, SharedItemSlotsComponent component, GetAlternativeVerbsEvent args)
|
||||
{
|
||||
if (args.Hands == null ||
|
||||
!args.CanAccess ||
|
||||
!args.CanInteract ||
|
||||
!_actionBlockerSystem.CanPickup(args.User.Uid))
|
||||
return;
|
||||
|
||||
foreach (var (slotName, slot) in component.Slots)
|
||||
{
|
||||
if (slot.ContainerSlot.ContainedEntity == null)
|
||||
continue;
|
||||
|
||||
Verb verb = new();
|
||||
verb.Text = slot.Name;
|
||||
verb.Category = VerbCategory.Eject;
|
||||
verb.Act = () => TryEjectContent(uid, slotName, args.User, component);
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInsertVerbs(EntityUid uid, SharedItemSlotsComponent component, GetInteractionVerbsEvent args)
|
||||
{
|
||||
if (args.Using == null ||
|
||||
!args.CanAccess ||
|
||||
!args.CanInteract ||
|
||||
!_actionBlockerSystem.CanDrop(args.User.Uid))
|
||||
return;
|
||||
|
||||
foreach (var (slotName, slot) in component.Slots)
|
||||
{
|
||||
if (!CanInsertContent(args.Using, slot))
|
||||
continue;
|
||||
|
||||
Verb verb = new();
|
||||
verb.Text = slot.Name != string.Empty ? slot.Name : args.Using.Name;
|
||||
verb.Category = VerbCategory.Insert;
|
||||
verb.Act = () => InsertContent(component, slot, slotName, args.Using);
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, SharedItemSlotsComponent itemSlots, InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = TryInsertContent(uid, args.Used, args.User, itemSlots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to insert or swap an item in any fitting item slot from users hand. If a valid slot already contains an item, it will swap it out.
|
||||
/// </summary>
|
||||
/// <returns>False if failed to insert item</returns>
|
||||
public bool TryInsertContent(EntityUid uid, IEntity item, IEntity user, SharedItemSlotsComponent? itemSlots = null, SharedHandsComponent? hands = null)
|
||||
{
|
||||
if (!Resolve(uid, ref itemSlots))
|
||||
return false;
|
||||
|
||||
if (!Resolve(user.Uid, ref hands))
|
||||
{
|
||||
itemSlots.Owner.PopupMessage(user, Loc.GetString("item-slots-try-insert-no-hands"));
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var (slotName, slot) in itemSlots.Slots)
|
||||
{
|
||||
// check if item allowed in whitelist
|
||||
if (slot.Whitelist != null && !slot.Whitelist.IsValid(item.Uid))
|
||||
continue;
|
||||
|
||||
// check if slot does not contain the item currently being inserted???
|
||||
if (slot.ContainerSlot.Contains(item))
|
||||
continue;
|
||||
|
||||
// get item inside container
|
||||
IEntity? swap = null;
|
||||
if (slot.ContainerSlot.ContainedEntity != null)
|
||||
swap = slot.ContainerSlot.ContainedEntity;
|
||||
|
||||
// return if user can't drop active item in hand
|
||||
if (!hands.Drop(item))
|
||||
return true;
|
||||
|
||||
// swap item in hand and item in slot
|
||||
if (swap != null)
|
||||
hands.TryPutInAnyHand(swap);
|
||||
|
||||
InsertContent(itemSlots, slot, slotName, item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void InsertContent(SharedItemSlotsComponent itemSlots, ItemSlot slot, string slotName, IEntity item)
|
||||
{
|
||||
// insert item
|
||||
slot.ContainerSlot.Insert(item);
|
||||
RaiseLocalEvent(itemSlots.OwnerUid, new ItemSlotChangedEvent(itemSlots, slotName, slot));
|
||||
|
||||
// play sound
|
||||
if (slot.InsertSound != null)
|
||||
SoundSystem.Play(Filter.Pvs(itemSlots.Owner), slot.InsertSound.GetSound(), itemSlots.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can a given item be inserted into a slot, without ejecting the current item in that slot.
|
||||
/// </summary>
|
||||
public bool CanInsertContent(IEntity item, ItemSlot slot)
|
||||
{
|
||||
if (slot.ContainerSlot.ContainedEntity != null)
|
||||
return false;
|
||||
|
||||
// check if item allowed in whitelist
|
||||
if (slot.Whitelist != null && !slot.Whitelist.IsValid(item.Uid))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to insert item in known slot. Doesn't interact with user
|
||||
/// </summary>
|
||||
/// <returns>False if failed to insert item</returns>
|
||||
public bool TryInsertContent(SharedItemSlotsComponent itemSlots, IEntity item, string slotName)
|
||||
{
|
||||
if (!itemSlots.Slots.TryGetValue(slotName, out var slot))
|
||||
return false;
|
||||
|
||||
if (!CanInsertContent(item, slot))
|
||||
return false;
|
||||
|
||||
InsertContent(itemSlots, slot, slotName, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if slot has some content in it (without ejecting item)
|
||||
/// </summary>
|
||||
/// <returns>Null if doesn't have any content</returns>
|
||||
public IEntity? PeekItemInSlot(SharedItemSlotsComponent itemSlots, string slotName)
|
||||
{
|
||||
if (!itemSlots.Slots.TryGetValue(slotName, out var slot))
|
||||
return null;
|
||||
|
||||
var item = slot.ContainerSlot.ContainedEntity;
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to eject item from slot to users hands
|
||||
/// </summary>
|
||||
public bool TryEjectContent(EntityUid uid, string slotName, IEntity? user, SharedItemSlotsComponent? itemSlots = null)
|
||||
{
|
||||
if (!Resolve(uid, ref itemSlots))
|
||||
return false;
|
||||
|
||||
if (!itemSlots.Slots.TryGetValue(slotName, out var slot))
|
||||
return false;
|
||||
|
||||
if (slot.ContainerSlot.ContainedEntity == null)
|
||||
return false;
|
||||
|
||||
var item = slot.ContainerSlot.ContainedEntity;
|
||||
if (!slot.ContainerSlot.Remove(item))
|
||||
return false;
|
||||
|
||||
// try eject item to users hand
|
||||
if (user != null)
|
||||
{
|
||||
if (user.TryGetComponent(out SharedHandsComponent? hands))
|
||||
{
|
||||
hands.TryPutInAnyHand(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
itemSlots.Owner.PopupMessage(user, Loc.GetString("item-slots-try-insert-no-hands"));
|
||||
}
|
||||
}
|
||||
|
||||
if (slot.EjectSound != null)
|
||||
SoundSystem.Play(Filter.Pvs(itemSlots.Owner), slot.EjectSound.GetSound(), itemSlots.Owner);
|
||||
|
||||
RaiseLocalEvent(itemSlots.OwnerUid, new ItemSlotChangedEvent(itemSlots, slotName, slot));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,2 @@
|
||||
item-slots-try-insert-no-hands = You have no hands.
|
||||
|
||||
# EjectItemVerb
|
||||
eject-item-verb-text-default = Eject {$item}
|
||||
take-item-verb-text = Take {$subject}
|
||||
place-item-verb-text = Place {$subject}
|
||||
|
||||
@@ -22,10 +22,7 @@
|
||||
mask:
|
||||
- VaultImpassable
|
||||
- type: Nuke
|
||||
- type: InteractionOutline
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
DiskSlot:
|
||||
diskSlot:
|
||||
name: Disk
|
||||
insertSound:
|
||||
path: /Audio/Machines/terminal_insert_disc.ogg
|
||||
@@ -34,6 +31,7 @@
|
||||
whitelist:
|
||||
tags:
|
||||
- NukeDisk
|
||||
- type: InteractionOutline
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.NukeUiKey.Key
|
||||
|
||||
@@ -34,23 +34,19 @@
|
||||
type: PDABoundUserInterface
|
||||
- key: enum.UplinkUiKey.Key
|
||||
type: UplinkBoundUserInterface
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
pdaPenSlot:
|
||||
item: "Pen"
|
||||
- type: PDA
|
||||
penSlot:
|
||||
startingItem: Pen
|
||||
whitelist:
|
||||
tags:
|
||||
- Write
|
||||
pdaIdSlot:
|
||||
idSlot:
|
||||
name: ID Card
|
||||
insertSound:
|
||||
path: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
|
||||
ejectSound:
|
||||
path: /Audio/Machines/id_swipe.ogg
|
||||
ejectSound: /Audio/Machines/id_swipe.ogg
|
||||
insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
|
||||
whitelist:
|
||||
components:
|
||||
- IdCard
|
||||
- type: PDA
|
||||
- type: DoorBumpOpener
|
||||
|
||||
- type: entity
|
||||
@@ -100,6 +96,15 @@
|
||||
components:
|
||||
- type: PDA
|
||||
idCard: ClownIDCard
|
||||
penSlot:
|
||||
startingItem: CrayonOrange # no pink crayon?!?
|
||||
# Maybe this is a bad idea.
|
||||
# At least they can't just spam alt-click it.
|
||||
# You need to remove the ID & alternate between inserting and ejecting
|
||||
ejectSound: /Audio/Items/bikehorn.ogg
|
||||
whitelist:
|
||||
tags:
|
||||
- Write
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
@@ -135,6 +140,11 @@
|
||||
components:
|
||||
- type: PDA
|
||||
idCard: MimeIDCard
|
||||
idSlot:
|
||||
name: ID Card
|
||||
whitelist:
|
||||
components:
|
||||
- IdCard
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: PDAVisualizer
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
capacity: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Write
|
||||
- Crayon
|
||||
- CrayonWhite
|
||||
|
||||
- type: entity
|
||||
@@ -52,6 +54,8 @@
|
||||
capacity: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Write
|
||||
- Crayon
|
||||
- CrayonWhite
|
||||
|
||||
- type: entity
|
||||
@@ -71,6 +75,8 @@
|
||||
capacity: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Write
|
||||
- Crayon
|
||||
- CrayonBlack
|
||||
|
||||
- type: entity
|
||||
@@ -90,6 +96,8 @@
|
||||
capacity: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Write
|
||||
- Crayon
|
||||
- CrayonRed
|
||||
|
||||
- type: entity
|
||||
@@ -109,6 +117,8 @@
|
||||
capacity: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Write
|
||||
- Crayon
|
||||
- CrayonOrange
|
||||
|
||||
- type: entity
|
||||
@@ -128,6 +138,8 @@
|
||||
capacity: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Write
|
||||
- Crayon
|
||||
- CrayonYellow
|
||||
|
||||
- type: entity
|
||||
@@ -147,6 +159,8 @@
|
||||
capacity: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Write
|
||||
- Crayon
|
||||
- CrayonGreen
|
||||
|
||||
- type: entity
|
||||
@@ -166,6 +180,8 @@
|
||||
capacity: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Write
|
||||
- Crayon
|
||||
- CrayonBlue
|
||||
|
||||
- type: entity
|
||||
@@ -185,6 +201,8 @@
|
||||
capacity: 5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Write
|
||||
- Crayon
|
||||
- CrayonPurple
|
||||
|
||||
- type: entity
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
openSound:
|
||||
path: /Audio/Misc/zip.ogg
|
||||
- type: PaperLabel
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
labelSlot:
|
||||
insertVerbText: Attach Label
|
||||
ejectVerbText: Remove Label
|
||||
whitelist:
|
||||
components:
|
||||
- Paper
|
||||
|
||||
@@ -54,9 +54,9 @@
|
||||
state_open: crate_open
|
||||
state_closed: crate_door
|
||||
- type: PaperLabel
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
labelSlot:
|
||||
insertVerbText: Attach Label
|
||||
ejectVerbText: Remove Label
|
||||
whitelist:
|
||||
components:
|
||||
- Paper
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
- state: closed
|
||||
map: ["enum.ItemCabinetVisualLayers.Door"]
|
||||
- type: ItemCabinet
|
||||
cabinetSlot:
|
||||
ejectOnInteract: true
|
||||
whitelist:
|
||||
components:
|
||||
- FireExtinguisher
|
||||
doorSound:
|
||||
path: /Audio/Machines/machine_switch.ogg
|
||||
- type: Appearance
|
||||
@@ -25,12 +30,6 @@
|
||||
- type: ItemCabinetVisualizer
|
||||
openState: open
|
||||
closedState: closed
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
cabinetSlot:
|
||||
whitelist:
|
||||
components:
|
||||
- FireExtinguisher
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
|
||||
@@ -48,11 +47,9 @@
|
||||
suffix: Filled
|
||||
components:
|
||||
- type: ItemCabinet
|
||||
spawnPrototype: FireExtinguisher
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
cabinetSlot:
|
||||
item: FireExtinguisher
|
||||
ejectOnInteract: true
|
||||
startingItem: FireExtinguisher
|
||||
whitelist:
|
||||
components:
|
||||
- FireExtinguisher
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
- state: glass
|
||||
map: ["enum.ItemCabinetVisualLayers.Door"]
|
||||
- type: ItemCabinet
|
||||
cabinetSlot:
|
||||
ejectOnInteract: true
|
||||
whitelist:
|
||||
tags:
|
||||
- FireAxe
|
||||
doorSound:
|
||||
path: /Audio/Machines/machine_switch.ogg
|
||||
- type: Appearance
|
||||
@@ -23,12 +28,6 @@
|
||||
- type: ItemCabinetVisualizer
|
||||
closedState: glass
|
||||
openState: glass-up
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
cabinetSlot:
|
||||
whitelist:
|
||||
tags:
|
||||
- FireAxe
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
|
||||
@@ -46,10 +45,9 @@
|
||||
suffix: Filled
|
||||
components:
|
||||
- type: ItemCabinet
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
cabinetSlot:
|
||||
item: FireAxe
|
||||
startingItem: FireAxe
|
||||
ejectOnInteract: true
|
||||
whitelist:
|
||||
tags:
|
||||
- FireAxe
|
||||
|
||||
BIN
Resources/Textures/Interface/VerbIcons/drop.svg.192dpi.png
Normal file
BIN
Resources/Textures/Interface/VerbIcons/drop.svg.192dpi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 587 B |
@@ -0,0 +1,4 @@
|
||||
# For now, this icon is literally just the pickup icon rotated 180 degrees.
|
||||
# But maybe this will change in the future?
|
||||
sample:
|
||||
filter: true
|
||||
Reference in New Issue
Block a user