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:
|
Slots:
|
||||||
- idcard
|
- idcard
|
||||||
- type: PDA
|
- type: PDA
|
||||||
idCard: AssistantIDCard
|
|
||||||
";
|
";
|
||||||
[Test]
|
[Test]
|
||||||
public async Task SpawnItemInSlotTest()
|
public async Task SpawnItemInSlotTest()
|
||||||
|
|||||||
@@ -29,13 +29,12 @@ namespace Content.IntegrationTests.Tests.PDA
|
|||||||
id: {PdaDummy}
|
id: {PdaDummy}
|
||||||
name: {PdaDummy}
|
name: {PdaDummy}
|
||||||
components:
|
components:
|
||||||
- type: ItemSlots
|
|
||||||
slots:
|
|
||||||
pdaIdSlot:
|
|
||||||
whitelist:
|
|
||||||
components:
|
|
||||||
- IdCard
|
|
||||||
- type: PDA
|
- type: PDA
|
||||||
|
idSlot:
|
||||||
|
name: ID Card
|
||||||
|
whitelist:
|
||||||
|
components:
|
||||||
|
- IdCard
|
||||||
- type: Item";
|
- type: Item";
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -77,9 +76,9 @@ namespace Content.IntegrationTests.Tests.PDA
|
|||||||
var pdaComponent = dummyPda.GetComponent<PDAComponent>();
|
var pdaComponent = dummyPda.GetComponent<PDAComponent>();
|
||||||
var pdaIdCard = sEntityManager.SpawnEntity(IdCardDummy, player.Transform.MapPosition);
|
var pdaIdCard = sEntityManager.SpawnEntity(IdCardDummy, player.Transform.MapPosition);
|
||||||
|
|
||||||
var itemSlots = dummyPda.GetComponent<SharedItemSlotsComponent>();
|
var itemSlots = dummyPda.GetComponent<ItemSlotsComponent>();
|
||||||
sEntityManager.EntitySysManager.GetEntitySystem<SharedItemSlotsSystem>()
|
sEntityManager.EntitySysManager.GetEntitySystem<ItemSlotsSystem>()
|
||||||
.TryInsertContent(itemSlots, pdaIdCard, pdaComponent.IdSlot);
|
.TryInsert(dummyPda.Uid, pdaComponent.IdSlot, pdaIdCard);
|
||||||
var pdaContainedId = pdaComponent.ContainedID;
|
var pdaContainedId = pdaComponent.ContainedID;
|
||||||
|
|
||||||
// The PDA in the hand should be found first
|
// The PDA in the hand should be found first
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ namespace Content.IntegrationTests.Tests.Utility
|
|||||||
|
|
||||||
// Test from serialized
|
// Test from serialized
|
||||||
var dummy = entityManager.SpawnEntity("WhitelistDummy", mapCoordinates);
|
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, Is.Not.Null);
|
||||||
|
|
||||||
Assert.That(whitelistSer.Components, Is.Not.Null);
|
Assert.That(whitelistSer.Components, Is.Not.Null);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Content.Shared.Sound;
|
using Content.Shared.Sound;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
@@ -21,10 +22,11 @@ namespace Content.Server.Cabinet
|
|||||||
public SoundSpecifier DoorSound { get; set; } = default!;
|
public SoundSpecifier DoorSound { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
[DataField("cabinetSlot")]
|
[DataField("cabinetSlot")]
|
||||||
public string CabinetSlot = "cabinetSlot";
|
public ItemSlot CabinetSlot = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the cabinet is currently open or not.
|
/// Whether the cabinet is currently open or not.
|
||||||
|
|||||||
@@ -4,54 +4,63 @@ using Content.Shared.Containers.ItemSlots;
|
|||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Content.Server.Cabinet
|
namespace Content.Server.Cabinet
|
||||||
{
|
{
|
||||||
public class ItemCabinetSystem : EntitySystem
|
public class ItemCabinetSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedItemSlotsSystem _itemSlotsSystem = default!;
|
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<ItemCabinetComponent, InteractUsingEvent>(OnInteractUsing);
|
SubscribeLocalEvent<ItemCabinetComponent, ComponentInit>(OnComponentInit);
|
||||||
SubscribeLocalEvent<ItemCabinetComponent, InteractHandEvent>(OnInteractHand);
|
SubscribeLocalEvent<ItemCabinetComponent, ComponentRemove>(OnComponentRemove);
|
||||||
|
SubscribeLocalEvent<ItemCabinetComponent, ComponentStartup>(OnComponentStartup);
|
||||||
|
|
||||||
SubscribeLocalEvent<ItemCabinetComponent, ActivateInWorldEvent>(OnActivateInWorld);
|
SubscribeLocalEvent<ItemCabinetComponent, ActivateInWorldEvent>(OnActivateInWorld);
|
||||||
SubscribeLocalEvent<ItemCabinetComponent, ComponentStartup>(InitializeAppearance);
|
|
||||||
SubscribeLocalEvent<ItemCabinetComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
|
|
||||||
SubscribeLocalEvent<ItemCabinetComponent, GetActivationVerbsEvent>(AddToggleOpenVerb);
|
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,
|
private void UpdateAppearance(EntityUid uid,
|
||||||
ItemCabinetComponent? cabinet = null,
|
ItemCabinetComponent? cabinet = null,
|
||||||
SharedItemSlotsComponent? itemSlots = null,
|
|
||||||
SharedAppearanceComponent? appearance = null)
|
SharedAppearanceComponent? appearance = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref cabinet, ref itemSlots, ref appearance, false))
|
if (!Resolve(uid, ref cabinet, ref appearance, false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
appearance.SetData(ItemCabinetVisuals.IsOpen, cabinet.Opened);
|
appearance.SetData(ItemCabinetVisuals.IsOpen, cabinet.Opened);
|
||||||
|
appearance.SetData(ItemCabinetVisuals.ContainsItem, cabinet.CabinetSlot.HasItem);
|
||||||
if (!itemSlots.Slots.TryGetValue(cabinet.CabinetSlot, out var slot))
|
|
||||||
return;
|
|
||||||
|
|
||||||
appearance.SetData(ItemCabinetVisuals.ContainsItem, slot.HasEntity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
private void AddToggleOpenVerb(EntityUid uid, ItemCabinetComponent cabinet, GetActivationVerbsEvent args)
|
||||||
@@ -75,44 +84,6 @@ namespace Content.Server.Cabinet
|
|||||||
args.Verbs.Add(toggleVerb);
|
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)
|
private void OnActivateInWorld(EntityUid uid, ItemCabinetComponent comp, ActivateInWorldEvent args)
|
||||||
{
|
{
|
||||||
if (args.Handled)
|
if (args.Handled)
|
||||||
@@ -132,6 +103,7 @@ namespace Content.Server.Cabinet
|
|||||||
|
|
||||||
cabinet.Opened = !cabinet.Opened;
|
cabinet.Opened = !cabinet.Opened;
|
||||||
SoundSystem.Play(Filter.Pvs(uid), cabinet.DoorSound.GetSound(), uid, AudioHelpers.WithVariation(0.15f));
|
SoundSystem.Play(Filter.Pvs(uid), cabinet.DoorSound.GetSound(), uid, AudioHelpers.WithVariation(0.15f));
|
||||||
|
_itemSlotsSystem.SetLock(uid, cabinet.CabinetSlot.ID, !cabinet.Opened);
|
||||||
|
|
||||||
UpdateAppearance(uid, cabinet);
|
UpdateAppearance(uid, cabinet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace Content.Server.Cargo.Components
|
|||||||
{
|
{
|
||||||
|
|
||||||
//This entire class is a PLACEHOLDER for the cargo shuttle.
|
//This entire class is a PLACEHOLDER for the cargo shuttle.
|
||||||
|
//welp only need auto-docking now.
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class CargoTelepadComponent : Component
|
public class CargoTelepadComponent : Component
|
||||||
@@ -136,10 +137,9 @@ namespace Content.Server.Cargo.Components
|
|||||||
("approver", data.Approver)));
|
("approver", data.Approver)));
|
||||||
|
|
||||||
// attempt to attach the label
|
// attempt to attach the label
|
||||||
if (_entityManager.TryGetComponent(product.Uid, out PaperLabelComponent label) &&
|
if (_entityManager.TryGetComponent(product.Uid, out PaperLabelComponent label))
|
||||||
_entityManager.TryGetComponent(product.Uid, out SharedItemSlotsComponent slots))
|
|
||||||
{
|
{
|
||||||
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.GameObjects;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
@@ -12,6 +13,6 @@ namespace Content.Server.Labels.Components
|
|||||||
public override string Name => "PaperLabel";
|
public override string Name => "PaperLabel";
|
||||||
|
|
||||||
[DataField("labelSlot")]
|
[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.Examine;
|
||||||
using Content.Shared.Labels;
|
using Content.Shared.Labels;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Content.Server.Labels
|
namespace Content.Server.Labels
|
||||||
{
|
{
|
||||||
@@ -18,26 +18,35 @@ namespace Content.Server.Labels
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class LabelSystem : EntitySystem
|
public class LabelSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedItemSlotsSystem _itemSlotsSystem = default!;
|
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<LabelComponent, ExaminedEvent>(OnExamine);
|
SubscribeLocalEvent<LabelComponent, ExaminedEvent>(OnExamine);
|
||||||
SubscribeLocalEvent<PaperLabelComponent, ComponentInit>(InitializePaperLabel);
|
SubscribeLocalEvent<PaperLabelComponent, ComponentInit>(OnComponentInit);
|
||||||
SubscribeLocalEvent<PaperLabelComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
|
SubscribeLocalEvent<PaperLabelComponent, ComponentRemove>(OnComponentRemove);
|
||||||
|
SubscribeLocalEvent<PaperLabelComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
|
||||||
|
SubscribeLocalEvent<PaperLabelComponent, EntRemovedFromContainerMessage>(OnContainerModified);
|
||||||
SubscribeLocalEvent<PaperLabelComponent, ExaminedEvent>(OnExamined);
|
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))
|
if (!EntityManager.TryGetComponent(uid, out SharedAppearanceComponent appearance))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
appearance.SetData(PaperLabelVisuals.HasLabel, false);
|
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)
|
private void OnExamine(EntityUid uid, LabelComponent? label, ExaminedEvent args)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref label))
|
if (!Resolve(uid, ref label))
|
||||||
@@ -53,12 +62,7 @@ namespace Content.Server.Labels
|
|||||||
|
|
||||||
private void OnExamined(EntityUid uid, PaperLabelComponent comp, ExaminedEvent args)
|
private void OnExamined(EntityUid uid, PaperLabelComponent comp, ExaminedEvent args)
|
||||||
{
|
{
|
||||||
if (!EntityManager.TryGetComponent(uid, out SharedItemSlotsComponent slots))
|
if (comp.LabelSlot.Item == null)
|
||||||
return;
|
|
||||||
|
|
||||||
var label = _itemSlotsSystem.PeekItemInSlot(slots, comp.LabelSlot);
|
|
||||||
|
|
||||||
if (label == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!args.IsInDetailsRange)
|
if (!args.IsInDetailsRange)
|
||||||
@@ -67,8 +71,8 @@ namespace Content.Server.Labels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent(label.Uid, out PaperComponent paper))
|
if (!EntityManager.TryGetComponent(comp.LabelSlot.Item.Uid, out PaperComponent paper))
|
||||||
// should never happen
|
// Assuming yaml has the correct entity whitelist, this should not happen.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(paper.Content))
|
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;
|
return;
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent(uid, out SharedAppearanceComponent appearance))
|
if (!EntityManager.TryGetComponent(uid, out SharedAppearanceComponent appearance))
|
||||||
return;
|
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;
|
public int Timer = 180;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Slot name for to store nuclear disk inside bomb.
|
/// The <see cref="ItemSlot"/> that stores the nuclear disk. The entity whitelist, sounds, and some other
|
||||||
/// See <see cref="SharedItemSlotsComponent"/> for mor info.
|
/// 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>
|
/// </summary>
|
||||||
[DataField("slot")]
|
[DataField("diskSlot")]
|
||||||
public string DiskSlotName = "DiskSlot";
|
public ItemSlot DiskSlot = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Annihilation radius in which all human players will be gibed
|
/// Annihilation radius in which all human players will be gibed
|
||||||
@@ -71,13 +72,6 @@ namespace Content.Server.Nuke
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public float RemainingTime;
|
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>
|
/// <summary>
|
||||||
/// Curent nuclear code buffer. Entered manually by players.
|
/// Curent nuclear code buffer. Entered manually by players.
|
||||||
/// If valid it will allow arm/disarm bomb.
|
/// If valid it will allow arm/disarm bomb.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using Content.Server.Coordinates.Helpers;
|
|||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
using Content.Shared.Sound;
|
using Content.Shared.Sound;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
|
||||||
namespace Content.Server.Nuke
|
namespace Content.Server.Nuke
|
||||||
{
|
{
|
||||||
@@ -26,7 +27,7 @@ namespace Content.Server.Nuke
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly NukeCodeSystem _codes = default!;
|
[Dependency] private readonly NukeCodeSystem _codes = default!;
|
||||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = 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 PopupSystem _popups = default!;
|
||||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||||
[Dependency] private readonly IChatManager _chat = default!;
|
[Dependency] private readonly IChatManager _chat = default!;
|
||||||
@@ -39,7 +40,8 @@ namespace Content.Server.Nuke
|
|||||||
SubscribeLocalEvent<NukeComponent, ComponentInit>(OnInit);
|
SubscribeLocalEvent<NukeComponent, ComponentInit>(OnInit);
|
||||||
SubscribeLocalEvent<NukeComponent, ComponentRemove>(OnRemove);
|
SubscribeLocalEvent<NukeComponent, ComponentRemove>(OnRemove);
|
||||||
SubscribeLocalEvent<NukeComponent, ActivateInWorldEvent>(OnActivate);
|
SubscribeLocalEvent<NukeComponent, ActivateInWorldEvent>(OnActivate);
|
||||||
SubscribeLocalEvent<NukeComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
|
SubscribeLocalEvent<NukeComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
|
||||||
|
SubscribeLocalEvent<NukeComponent, EntRemovedFromContainerMessage>(OnItemSlotChanged);
|
||||||
|
|
||||||
// anchoring logic
|
// anchoring logic
|
||||||
SubscribeLocalEvent<NukeComponent, AnchorAttemptEvent>(OnAnchorAttempt);
|
SubscribeLocalEvent<NukeComponent, AnchorAttemptEvent>(OnAnchorAttempt);
|
||||||
@@ -59,6 +61,7 @@ namespace Content.Server.Nuke
|
|||||||
private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args)
|
private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args)
|
||||||
{
|
{
|
||||||
component.RemainingTime = component.Timer;
|
component.RemainingTime = component.Timer;
|
||||||
|
_itemSlots.AddItemSlot(uid, component.Name, component.DiskSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
@@ -93,14 +96,14 @@ namespace Content.Server.Nuke
|
|||||||
private void OnRemove(EntityUid uid, NukeComponent component, ComponentRemove args)
|
private void OnRemove(EntityUid uid, NukeComponent component, ComponentRemove args)
|
||||||
{
|
{
|
||||||
_tickingBombs.Remove(uid);
|
_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;
|
return;
|
||||||
|
|
||||||
component.DiskInserted = args.ContainedItem != null;
|
|
||||||
UpdateStatus(uid, component);
|
UpdateStatus(uid, component);
|
||||||
UpdateUserInterface(uid, component);
|
UpdateUserInterface(uid, component);
|
||||||
}
|
}
|
||||||
@@ -137,7 +140,7 @@ namespace Content.Server.Nuke
|
|||||||
private void CheckAnchorAttempt(EntityUid uid, NukeComponent component, BaseAnchoredAttemptEvent args)
|
private void CheckAnchorAttempt(EntityUid uid, NukeComponent component, BaseAnchoredAttemptEvent args)
|
||||||
{
|
{
|
||||||
// cancel any anchor attempt without nuke disk
|
// cancel any anchor attempt without nuke disk
|
||||||
if (!component.DiskInserted)
|
if (!component.DiskSlot.HasItem)
|
||||||
{
|
{
|
||||||
var msg = Loc.GetString("nuke-component-cant-anchor");
|
var msg = Loc.GetString("nuke-component-cant-anchor");
|
||||||
_popups.PopupEntity(msg, uid, Filter.Entities(args.User));
|
_popups.PopupEntity(msg, uid, Filter.Entities(args.User));
|
||||||
@@ -160,15 +163,15 @@ namespace Content.Server.Nuke
|
|||||||
#region UI Events
|
#region UI Events
|
||||||
private void OnEjectButtonPressed(EntityUid uid, NukeComponent component, NukeEjectMessage args)
|
private void OnEjectButtonPressed(EntityUid uid, NukeComponent component, NukeEjectMessage args)
|
||||||
{
|
{
|
||||||
if (!component.DiskInserted)
|
if (!component.DiskSlot.HasItem)
|
||||||
return;
|
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)
|
private async void OnAnchorButtonPressed(EntityUid uid, NukeComponent component, NukeAnchorMessage args)
|
||||||
{
|
{
|
||||||
if (!component.DiskInserted)
|
if (!component.DiskSlot.HasItem)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent(uid, out TransformComponent? transform))
|
if (!EntityManager.TryGetComponent(uid, out TransformComponent? transform))
|
||||||
@@ -218,7 +221,7 @@ namespace Content.Server.Nuke
|
|||||||
|
|
||||||
private void OnArmButtonPressed(EntityUid uid, NukeComponent component, NukeArmedMessage args)
|
private void OnArmButtonPressed(EntityUid uid, NukeComponent component, NukeArmedMessage args)
|
||||||
{
|
{
|
||||||
if (!component.DiskInserted)
|
if (!component.DiskSlot.HasItem)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (component.Status == NukeStatus.AWAIT_ARM)
|
if (component.Status == NukeStatus.AWAIT_ARM)
|
||||||
@@ -240,12 +243,12 @@ namespace Content.Server.Nuke
|
|||||||
switch (component.Status)
|
switch (component.Status)
|
||||||
{
|
{
|
||||||
case NukeStatus.AWAIT_DISK:
|
case NukeStatus.AWAIT_DISK:
|
||||||
if (component.DiskInserted)
|
if (component.DiskSlot.HasItem)
|
||||||
component.Status = NukeStatus.AWAIT_CODE;
|
component.Status = NukeStatus.AWAIT_CODE;
|
||||||
break;
|
break;
|
||||||
case NukeStatus.AWAIT_CODE:
|
case NukeStatus.AWAIT_CODE:
|
||||||
{
|
{
|
||||||
if (!component.DiskInserted)
|
if (!component.DiskSlot.HasItem)
|
||||||
{
|
{
|
||||||
component.Status = NukeStatus.AWAIT_DISK;
|
component.Status = NukeStatus.AWAIT_DISK;
|
||||||
component.EnteredCode = "";
|
component.EnteredCode = "";
|
||||||
@@ -299,7 +302,7 @@ namespace Content.Server.Nuke
|
|||||||
if (EntityManager.TryGetComponent(uid, out TransformComponent transform))
|
if (EntityManager.TryGetComponent(uid, out TransformComponent transform))
|
||||||
anchored = transform.Anchored;
|
anchored = transform.Anchored;
|
||||||
|
|
||||||
var allowArm = component.DiskInserted &&
|
var allowArm = component.DiskSlot.HasItem &&
|
||||||
(component.Status == NukeStatus.AWAIT_ARM ||
|
(component.Status == NukeStatus.AWAIT_ARM ||
|
||||||
component.Status == NukeStatus.ARMED);
|
component.Status == NukeStatus.ARMED);
|
||||||
|
|
||||||
@@ -307,7 +310,7 @@ namespace Content.Server.Nuke
|
|||||||
{
|
{
|
||||||
Status = component.Status,
|
Status = component.Status,
|
||||||
RemainingTime = (int) component.RemainingTime,
|
RemainingTime = (int) component.RemainingTime,
|
||||||
DiskInserted = component.DiskInserted,
|
DiskInserted = component.DiskSlot.HasItem,
|
||||||
IsAnchored = anchored,
|
IsAnchored = anchored,
|
||||||
AllowArm = allowArm,
|
AllowArm = allowArm,
|
||||||
EnteredCodeLength = component.EnteredCode.Length,
|
EnteredCodeLength = component.EnteredCode.Length,
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.Access.Components;
|
using Content.Server.Access.Components;
|
||||||
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.PDA
|
namespace Content.Server.PDA
|
||||||
@@ -12,16 +15,19 @@ namespace Content.Server.PDA
|
|||||||
{
|
{
|
||||||
public override string Name => "PDA";
|
public override string Name => "PDA";
|
||||||
|
|
||||||
[DataField("idSlot")]
|
[DataField("idSlot")]
|
||||||
public string IdSlot = "pdaIdSlot";
|
public ItemSlot IdSlot = new();
|
||||||
|
|
||||||
[DataField("penSlot")]
|
[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 IdCardComponent? ContainedID;
|
||||||
[ViewVariables] public bool PenInserted;
|
|
||||||
[ViewVariables] public bool FlashlightOn;
|
[ViewVariables] public bool FlashlightOn;
|
||||||
|
|
||||||
[ViewVariables] public string? OwnerName;
|
[ViewVariables] public string? OwnerName;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Content.Shared.Containers.ItemSlots;
|
|||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.PDA;
|
using Content.Shared.PDA;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ namespace Content.Server.PDA
|
|||||||
{
|
{
|
||||||
public class PDASystem : EntitySystem
|
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 UplinkSystem _uplinkSystem = default!;
|
||||||
[Dependency] private readonly UnpoweredFlashlightSystem _unpoweredFlashlight = default!;
|
[Dependency] private readonly UnpoweredFlashlightSystem _unpoweredFlashlight = default!;
|
||||||
|
|
||||||
@@ -25,10 +26,12 @@ namespace Content.Server.PDA
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<PDAComponent, ComponentInit>(OnComponentInit);
|
SubscribeLocalEvent<PDAComponent, ComponentInit>(OnComponentInit);
|
||||||
SubscribeLocalEvent<PDAComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<PDAComponent, ComponentRemove>(OnComponentRemove);
|
||||||
|
|
||||||
SubscribeLocalEvent<PDAComponent, ActivateInWorldEvent>(OnActivateInWorld);
|
SubscribeLocalEvent<PDAComponent, ActivateInWorldEvent>(OnActivateInWorld);
|
||||||
SubscribeLocalEvent<PDAComponent, UseInHandEvent>(OnUse);
|
SubscribeLocalEvent<PDAComponent, UseInHandEvent>(OnUse);
|
||||||
SubscribeLocalEvent<PDAComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
|
SubscribeLocalEvent<PDAComponent, EntInsertedIntoContainerMessage>(OnItemInserted);
|
||||||
|
SubscribeLocalEvent<PDAComponent, EntRemovedFromContainerMessage>(OnItemRemoved);
|
||||||
SubscribeLocalEvent<PDAComponent, LightToggleEvent>(OnLightToggle);
|
SubscribeLocalEvent<PDAComponent, LightToggleEvent>(OnLightToggle);
|
||||||
|
|
||||||
SubscribeLocalEvent<PDAComponent, UplinkInitEvent>(OnUplinkInit);
|
SubscribeLocalEvent<PDAComponent, UplinkInitEvent>(OnUplinkInit);
|
||||||
@@ -41,19 +44,16 @@ namespace Content.Server.PDA
|
|||||||
if (ui != null)
|
if (ui != null)
|
||||||
ui.OnReceiveMessage += (msg) => OnUIMessage(pda, msg);
|
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
|
_itemSlotsSystem.RemoveItemSlot(uid, pda.IdSlot);
|
||||||
if (!string.IsNullOrEmpty(pda.StartingIdCard))
|
_itemSlotsSystem.RemoveItemSlot(uid, pda.PenSlot);
|
||||||
{
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUse(EntityUid uid, PDAComponent pda, UseInHandEvent args)
|
private void OnUse(EntityUid uid, PDAComponent pda, UseInHandEvent args)
|
||||||
@@ -70,22 +70,19 @@ namespace Content.Server.PDA
|
|||||||
args.Handled = OpenUI(pda, args.User);
|
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.Container.ID == pda.IdSlot.ID)
|
||||||
if (args.SlotName == pda.IdSlot)
|
pda.ContainedID = args.Entity.GetComponentOrNull<IdCardComponent>();
|
||||||
{
|
|
||||||
var item = args.ContainedItem;
|
UpdatePDAAppearance(pda);
|
||||||
if (item == null || !EntityManager.TryGetComponent(item.Value, out IdCardComponent ? idCard))
|
UpdatePDAUserInterface(pda);
|
||||||
pda.ContainedID = null;
|
}
|
||||||
else
|
|
||||||
pda.ContainedID = idCard;
|
private void OnItemRemoved(EntityUid uid, PDAComponent pda, EntRemovedFromContainerMessage args)
|
||||||
}
|
{
|
||||||
else if (args.SlotName == pda.PenSlot)
|
if (args.Container.ID == pda.IdSlot.ID)
|
||||||
{
|
pda.ContainedID = null;
|
||||||
var item = args.ContainedItem;
|
|
||||||
pda.PenInserted = item != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdatePDAAppearance(pda);
|
UpdatePDAAppearance(pda);
|
||||||
UpdatePDAUserInterface(pda);
|
UpdatePDAUserInterface(pda);
|
||||||
@@ -142,11 +139,15 @@ namespace Content.Server.PDA
|
|||||||
var hasUplink = pda.Owner.HasComponent<UplinkComponent>();
|
var hasUplink = pda.Owner.HasComponent<UplinkComponent>();
|
||||||
|
|
||||||
var ui = pda.Owner.GetUIOrNull(PDAUiKey.Key);
|
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)
|
private void OnUIMessage(PDAComponent pda, ServerBoundUserInterfaceMessage msg)
|
||||||
{
|
{
|
||||||
|
// cast EntityUid? to EntityUid
|
||||||
|
if (msg.Session.AttachedEntityUid is not EntityUid playerUid)
|
||||||
|
return;
|
||||||
|
|
||||||
switch (msg.Message)
|
switch (msg.Message)
|
||||||
{
|
{
|
||||||
case PDARequestUpdateInterfaceMessage _:
|
case PDARequestUpdateInterfaceMessage _:
|
||||||
@@ -161,12 +162,12 @@ namespace Content.Server.PDA
|
|||||||
|
|
||||||
case PDAEjectIDMessage _:
|
case PDAEjectIDMessage _:
|
||||||
{
|
{
|
||||||
_slotsSystem.TryEjectContent(pda.Owner.Uid, pda.IdSlot, msg.Session.AttachedEntity);
|
_itemSlotsSystem.TryEjectToHands(pda.Owner.Uid, pda.IdSlot, playerUid);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PDAEjectPenMessage _:
|
case PDAEjectPenMessage _:
|
||||||
{
|
{
|
||||||
_slotsSystem.TryEjectContent(pda.Owner.Uid, pda.PenSlot, msg.Session.AttachedEntity);
|
_itemSlotsSystem.TryEjectToHands(pda.Owner.Uid, pda.PenSlot, playerUid);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PDAShowUplinkMessage _:
|
case PDAShowUplinkMessage _:
|
||||||
|
|||||||
@@ -136,10 +136,10 @@ namespace Content.Server.Sandbox
|
|||||||
if (pda.ContainedID == null)
|
if (pda.ContainedID == null)
|
||||||
{
|
{
|
||||||
var newID = CreateFreshId();
|
var newID = CreateFreshId();
|
||||||
if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots))
|
if (pda.Owner.TryGetComponent(out ItemSlotsComponent? itemSlots))
|
||||||
{
|
{
|
||||||
_entityManager.EntitySysManager.GetEntitySystem<SharedItemSlotsSystem>().
|
_entityManager.EntitySysManager.GetEntitySystem<ItemSlotsSystem>().
|
||||||
TryInsertContent(itemSlots, newID, pda.IdSlot);
|
TryInsert(wornItem.Owner.Uid, pda.IdSlot, newID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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.
|
take-item-verb-text = Take {$subject}
|
||||||
|
place-item-verb-text = Place {$subject}
|
||||||
# EjectItemVerb
|
|
||||||
eject-item-verb-text-default = Eject {$item}
|
|
||||||
|
|||||||
@@ -22,18 +22,16 @@
|
|||||||
mask:
|
mask:
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
- type: Nuke
|
- type: Nuke
|
||||||
|
diskSlot:
|
||||||
|
name: Disk
|
||||||
|
insertSound:
|
||||||
|
path: /Audio/Machines/terminal_insert_disc.ogg
|
||||||
|
ejectSound:
|
||||||
|
path: /Audio/Machines/terminal_insert_disc.ogg
|
||||||
|
whitelist:
|
||||||
|
tags:
|
||||||
|
- NukeDisk
|
||||||
- type: InteractionOutline
|
- type: InteractionOutline
|
||||||
- type: ItemSlots
|
|
||||||
slots:
|
|
||||||
DiskSlot:
|
|
||||||
name: Disk
|
|
||||||
insertSound:
|
|
||||||
path: /Audio/Machines/terminal_insert_disc.ogg
|
|
||||||
ejectSound:
|
|
||||||
path: /Audio/Machines/terminal_insert_disc.ogg
|
|
||||||
whitelist:
|
|
||||||
tags:
|
|
||||||
- NukeDisk
|
|
||||||
- type: UserInterface
|
- type: UserInterface
|
||||||
interfaces:
|
interfaces:
|
||||||
- key: enum.NukeUiKey.Key
|
- key: enum.NukeUiKey.Key
|
||||||
|
|||||||
@@ -34,23 +34,19 @@
|
|||||||
type: PDABoundUserInterface
|
type: PDABoundUserInterface
|
||||||
- key: enum.UplinkUiKey.Key
|
- key: enum.UplinkUiKey.Key
|
||||||
type: UplinkBoundUserInterface
|
type: UplinkBoundUserInterface
|
||||||
- type: ItemSlots
|
|
||||||
slots:
|
|
||||||
pdaPenSlot:
|
|
||||||
item: "Pen"
|
|
||||||
whitelist:
|
|
||||||
tags:
|
|
||||||
- Write
|
|
||||||
pdaIdSlot:
|
|
||||||
name: ID Card
|
|
||||||
insertSound:
|
|
||||||
path: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
|
|
||||||
ejectSound:
|
|
||||||
path: /Audio/Machines/id_swipe.ogg
|
|
||||||
whitelist:
|
|
||||||
components:
|
|
||||||
- IdCard
|
|
||||||
- type: PDA
|
- type: PDA
|
||||||
|
penSlot:
|
||||||
|
startingItem: Pen
|
||||||
|
whitelist:
|
||||||
|
tags:
|
||||||
|
- Write
|
||||||
|
idSlot:
|
||||||
|
name: ID Card
|
||||||
|
ejectSound: /Audio/Machines/id_swipe.ogg
|
||||||
|
insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
|
||||||
|
whitelist:
|
||||||
|
components:
|
||||||
|
- IdCard
|
||||||
- type: DoorBumpOpener
|
- type: DoorBumpOpener
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -100,6 +96,15 @@
|
|||||||
components:
|
components:
|
||||||
- type: PDA
|
- type: PDA
|
||||||
idCard: ClownIDCard
|
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
|
- type: Appearance
|
||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
@@ -135,6 +140,11 @@
|
|||||||
components:
|
components:
|
||||||
- type: PDA
|
- type: PDA
|
||||||
idCard: MimeIDCard
|
idCard: MimeIDCard
|
||||||
|
idSlot:
|
||||||
|
name: ID Card
|
||||||
|
whitelist:
|
||||||
|
components:
|
||||||
|
- IdCard
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
visuals:
|
visuals:
|
||||||
- type: PDAVisualizer
|
- type: PDAVisualizer
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
capacity: 5
|
capacity: 5
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
|
- Write
|
||||||
|
- Crayon
|
||||||
- CrayonWhite
|
- CrayonWhite
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -52,6 +54,8 @@
|
|||||||
capacity: 5
|
capacity: 5
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
|
- Write
|
||||||
|
- Crayon
|
||||||
- CrayonWhite
|
- CrayonWhite
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -71,6 +75,8 @@
|
|||||||
capacity: 5
|
capacity: 5
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
|
- Write
|
||||||
|
- Crayon
|
||||||
- CrayonBlack
|
- CrayonBlack
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -90,6 +96,8 @@
|
|||||||
capacity: 5
|
capacity: 5
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
|
- Write
|
||||||
|
- Crayon
|
||||||
- CrayonRed
|
- CrayonRed
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -109,6 +117,8 @@
|
|||||||
capacity: 5
|
capacity: 5
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
|
- Write
|
||||||
|
- Crayon
|
||||||
- CrayonOrange
|
- CrayonOrange
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -128,6 +138,8 @@
|
|||||||
capacity: 5
|
capacity: 5
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
|
- Write
|
||||||
|
- Crayon
|
||||||
- CrayonYellow
|
- CrayonYellow
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -147,6 +159,8 @@
|
|||||||
capacity: 5
|
capacity: 5
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
|
- Write
|
||||||
|
- Crayon
|
||||||
- CrayonGreen
|
- CrayonGreen
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -166,6 +180,8 @@
|
|||||||
capacity: 5
|
capacity: 5
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
|
- Write
|
||||||
|
- Crayon
|
||||||
- CrayonBlue
|
- CrayonBlue
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -185,6 +201,8 @@
|
|||||||
capacity: 5
|
capacity: 5
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
|
- Write
|
||||||
|
- Crayon
|
||||||
- CrayonPurple
|
- CrayonPurple
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
|
|||||||
@@ -34,12 +34,12 @@
|
|||||||
openSound:
|
openSound:
|
||||||
path: /Audio/Misc/zip.ogg
|
path: /Audio/Misc/zip.ogg
|
||||||
- type: PaperLabel
|
- type: PaperLabel
|
||||||
- type: ItemSlots
|
labelSlot:
|
||||||
slots:
|
insertVerbText: Attach Label
|
||||||
labelSlot:
|
ejectVerbText: Remove Label
|
||||||
whitelist:
|
whitelist:
|
||||||
components:
|
components:
|
||||||
- Paper
|
- Paper
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
visuals:
|
visuals:
|
||||||
- type: StorageVisualizer
|
- type: StorageVisualizer
|
||||||
|
|||||||
@@ -54,9 +54,9 @@
|
|||||||
state_open: crate_open
|
state_open: crate_open
|
||||||
state_closed: crate_door
|
state_closed: crate_door
|
||||||
- type: PaperLabel
|
- type: PaperLabel
|
||||||
- type: ItemSlots
|
labelSlot:
|
||||||
slots:
|
insertVerbText: Attach Label
|
||||||
labelSlot:
|
ejectVerbText: Remove Label
|
||||||
whitelist:
|
whitelist:
|
||||||
components:
|
components:
|
||||||
- Paper
|
- Paper
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
- state: closed
|
- state: closed
|
||||||
map: ["enum.ItemCabinetVisualLayers.Door"]
|
map: ["enum.ItemCabinetVisualLayers.Door"]
|
||||||
- type: ItemCabinet
|
- type: ItemCabinet
|
||||||
|
cabinetSlot:
|
||||||
|
ejectOnInteract: true
|
||||||
|
whitelist:
|
||||||
|
components:
|
||||||
|
- FireExtinguisher
|
||||||
doorSound:
|
doorSound:
|
||||||
path: /Audio/Machines/machine_switch.ogg
|
path: /Audio/Machines/machine_switch.ogg
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
@@ -25,12 +30,6 @@
|
|||||||
- type: ItemCabinetVisualizer
|
- type: ItemCabinetVisualizer
|
||||||
openState: open
|
openState: open
|
||||||
closedState: closed
|
closedState: closed
|
||||||
- type: ItemSlots
|
|
||||||
slots:
|
|
||||||
cabinetSlot:
|
|
||||||
whitelist:
|
|
||||||
components:
|
|
||||||
- FireExtinguisher
|
|
||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
|
|
||||||
@@ -48,14 +47,12 @@
|
|||||||
suffix: Filled
|
suffix: Filled
|
||||||
components:
|
components:
|
||||||
- type: ItemCabinet
|
- type: ItemCabinet
|
||||||
spawnPrototype: FireExtinguisher
|
cabinetSlot:
|
||||||
- type: ItemSlots
|
ejectOnInteract: true
|
||||||
slots:
|
startingItem: FireExtinguisher
|
||||||
cabinetSlot:
|
whitelist:
|
||||||
item: FireExtinguisher
|
components:
|
||||||
whitelist:
|
- FireExtinguisher
|
||||||
components:
|
|
||||||
- FireExtinguisher
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: ExtinguisherCabinetFilledOpen
|
id: ExtinguisherCabinetFilledOpen
|
||||||
|
|||||||
@@ -16,6 +16,11 @@
|
|||||||
- state: glass
|
- state: glass
|
||||||
map: ["enum.ItemCabinetVisualLayers.Door"]
|
map: ["enum.ItemCabinetVisualLayers.Door"]
|
||||||
- type: ItemCabinet
|
- type: ItemCabinet
|
||||||
|
cabinetSlot:
|
||||||
|
ejectOnInteract: true
|
||||||
|
whitelist:
|
||||||
|
tags:
|
||||||
|
- FireAxe
|
||||||
doorSound:
|
doorSound:
|
||||||
path: /Audio/Machines/machine_switch.ogg
|
path: /Audio/Machines/machine_switch.ogg
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
@@ -23,12 +28,6 @@
|
|||||||
- type: ItemCabinetVisualizer
|
- type: ItemCabinetVisualizer
|
||||||
closedState: glass
|
closedState: glass
|
||||||
openState: glass-up
|
openState: glass-up
|
||||||
- type: ItemSlots
|
|
||||||
slots:
|
|
||||||
cabinetSlot:
|
|
||||||
whitelist:
|
|
||||||
tags:
|
|
||||||
- FireAxe
|
|
||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
|
|
||||||
@@ -46,13 +45,12 @@
|
|||||||
suffix: Filled
|
suffix: Filled
|
||||||
components:
|
components:
|
||||||
- type: ItemCabinet
|
- type: ItemCabinet
|
||||||
- type: ItemSlots
|
cabinetSlot:
|
||||||
slots:
|
startingItem: FireAxe
|
||||||
cabinetSlot:
|
ejectOnInteract: true
|
||||||
item: FireAxe
|
whitelist:
|
||||||
whitelist:
|
tags:
|
||||||
tags:
|
- FireAxe
|
||||||
- FireAxe
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: FireAxeCabinetFilledOpen
|
id: FireAxeCabinetFilledOpen
|
||||||
|
|||||||
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