Network item slots (#9199)
This commit is contained in:
@@ -2,6 +2,7 @@ using Content.Shared.Sound;
|
|||||||
using Content.Shared.Whitelist;
|
using Content.Shared.Whitelist;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
@@ -14,6 +15,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[Access(typeof(ItemSlotsSystem))]
|
[Access(typeof(ItemSlotsSystem))]
|
||||||
|
[NetworkedComponent]
|
||||||
public sealed class ItemSlotsComponent : Component
|
public sealed class ItemSlotsComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -42,16 +44,11 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class ItemSlotsComponentState : ComponentState
|
public sealed class ItemSlotsComponentState : ComponentState
|
||||||
{
|
{
|
||||||
public readonly Dictionary<string, bool> SlotLocked;
|
public readonly Dictionary<string, ItemSlot> Slots;
|
||||||
|
|
||||||
public ItemSlotsComponentState(Dictionary<string, ItemSlot> slots)
|
public ItemSlotsComponentState(Dictionary<string, ItemSlot> slots)
|
||||||
{
|
{
|
||||||
SlotLocked = new(slots.Count);
|
Slots = slots;
|
||||||
|
|
||||||
foreach (var (key, slot) in slots)
|
|
||||||
{
|
|
||||||
SlotLocked[key] = slot.Locked;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,8 +58,17 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DataDefinition]
|
[DataDefinition]
|
||||||
[Access(typeof(ItemSlotsSystem))]
|
[Access(typeof(ItemSlotsSystem))]
|
||||||
|
[Serializable, NetSerializable]
|
||||||
public sealed class ItemSlot
|
public sealed class ItemSlot
|
||||||
{
|
{
|
||||||
|
public ItemSlot() { }
|
||||||
|
|
||||||
|
public ItemSlot(ItemSlot other)
|
||||||
|
{
|
||||||
|
CopyFrom(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[DataField("whitelist")]
|
[DataField("whitelist")]
|
||||||
public EntityWhitelist? Whitelist;
|
public EntityWhitelist? Whitelist;
|
||||||
|
|
||||||
@@ -76,6 +82,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// Options used for playing the insert/eject sounds.
|
/// Options used for playing the insert/eject sounds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("soundOptions")]
|
[DataField("soundOptions")]
|
||||||
|
[Obsolete("Use the sound specifer parameters instead")]
|
||||||
public AudioParams SoundOptions = AudioParams.Default;
|
public AudioParams SoundOptions = AudioParams.Default;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -99,6 +106,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DataField("startingItem", readOnly: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
[DataField("startingItem", readOnly: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||||
[Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
[Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||||
|
[NonSerialized]
|
||||||
public string? StartingItem;
|
public string? StartingItem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -156,7 +164,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
[DataField("ejectVerbText")]
|
[DataField("ejectVerbText")]
|
||||||
public string? EjectVerbText;
|
public string? EjectVerbText;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables, NonSerialized]
|
||||||
public ContainerSlot? ContainerSlot = default!;
|
public ContainerSlot? ContainerSlot = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -167,6 +175,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// The actual deconstruction logic is handled by the server-side EmptyOnMachineDeconstructSystem.
|
/// The actual deconstruction logic is handled by the server-side EmptyOnMachineDeconstructSystem.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DataField("ejectOnDeconstruct")]
|
[DataField("ejectOnDeconstruct")]
|
||||||
|
[NonSerialized]
|
||||||
public bool EjectOnDeconstruct = true;
|
public bool EjectOnDeconstruct = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -174,6 +183,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// ejected when it is broken or destroyed?
|
/// ejected when it is broken or destroyed?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("ejectOnBreak")]
|
[DataField("ejectOnBreak")]
|
||||||
|
[NonSerialized]
|
||||||
public bool EjectOnBreak = false;
|
public bool EjectOnBreak = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -204,5 +214,32 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("priority")]
|
[DataField("priority")]
|
||||||
public int Priority = 0;
|
public int Priority = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If false, errors when adding an item slot with a duplicate key are suppressed. Local==true implies that
|
||||||
|
/// the slot was added via client component state handling.
|
||||||
|
/// </summary>
|
||||||
|
[NonSerialized]
|
||||||
|
public bool Local = true;
|
||||||
|
|
||||||
|
public void CopyFrom(ItemSlot other)
|
||||||
|
{
|
||||||
|
// These fields are mutable reference types. But they generally don't get modified, so this should be fine.
|
||||||
|
Whitelist = other.Whitelist;
|
||||||
|
InsertSound = other.InsertSound;
|
||||||
|
EjectSound = other.EjectSound;
|
||||||
|
|
||||||
|
SoundOptions = other.SoundOptions;
|
||||||
|
Name = other.Name;
|
||||||
|
Locked = other.Locked;
|
||||||
|
InsertOnInteract = other.InsertOnInteract;
|
||||||
|
EjectOnInteract = other.EjectOnInteract;
|
||||||
|
EjectOnUse = other.EjectOnUse;
|
||||||
|
InsertVerbText = other.InsertVerbText;
|
||||||
|
EjectVerbText = other.EjectVerbText;
|
||||||
|
WhitelistFailPopup = other.WhitelistFailPopup;
|
||||||
|
Swap = other.Swap;
|
||||||
|
Priority = other.Priority;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ using Robust.Shared.Player;
|
|||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Content.Shared.Destructible;
|
using Content.Shared.Destructible;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Containers.ItemSlots
|
namespace Content.Shared.Containers.ItemSlots
|
||||||
{
|
{
|
||||||
@@ -25,6 +27,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||||
|
[Dependency] private readonly INetManager _netMan = default!;
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -81,13 +84,23 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
/// Given a new item slot, store it in the <see cref="ItemSlotsComponent"/> and ensure the slot has an item
|
/// Given a new item slot, store it in the <see cref="ItemSlotsComponent"/> and ensure the slot has an item
|
||||||
/// container.
|
/// container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddItemSlot(EntityUid uid, string id, ItemSlot slot)
|
public void AddItemSlot(EntityUid uid, string id, ItemSlot slot, ItemSlotsComponent? itemSlots = null)
|
||||||
{
|
{
|
||||||
var itemSlots = EntityManager.EnsureComponent<ItemSlotsComponent>(uid);
|
itemSlots ??= EntityManager.EnsureComponent<ItemSlotsComponent>(uid);
|
||||||
|
DebugTools.Assert(itemSlots.Owner == uid);
|
||||||
|
|
||||||
|
if (itemSlots.Slots.TryGetValue(id, out var existing))
|
||||||
|
{
|
||||||
|
if (existing.Local)
|
||||||
|
Logger.Error($"Duplicate item slot key. Entity: {EntityManager.GetComponent<MetaDataComponent>(itemSlots.Owner).EntityName} ({uid}), key: {id}");
|
||||||
|
else
|
||||||
|
// server state takes priority
|
||||||
|
slot.CopyFrom(existing);
|
||||||
|
}
|
||||||
|
|
||||||
slot.ContainerSlot = _containers.EnsureContainer<ContainerSlot>(itemSlots.Owner, id);
|
slot.ContainerSlot = _containers.EnsureContainer<ContainerSlot>(itemSlots.Owner, id);
|
||||||
if (itemSlots.Slots.ContainsKey(id))
|
|
||||||
Logger.Error($"Duplicate item slot key. Entity: {EntityManager.GetComponent<MetaDataComponent>(itemSlots.Owner).EntityName} ({uid}), key: {id}");
|
|
||||||
itemSlots.Slots[id] = slot;
|
itemSlots.Slots[id] = slot;
|
||||||
|
Dirty(itemSlots);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -110,6 +123,8 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
|
|
||||||
if (itemSlots.Slots.Count == 0)
|
if (itemSlots.Slots.Count == 0)
|
||||||
EntityManager.RemoveComponent(uid, itemSlots);
|
EntityManager.RemoveComponent(uid, itemSlots);
|
||||||
|
else
|
||||||
|
Dirty(itemSlots);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -128,7 +143,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
TryEjectToHands(uid, slot, args.User);
|
TryEjectToHands(uid, slot, args.User, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,7 +162,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
TryEjectToHands(uid, slot, args.User);
|
TryEjectToHands(uid, slot, args.User, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,10 +234,10 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
{
|
{
|
||||||
if (sound == null || !_gameTiming.IsFirstTimePredicted)
|
if (sound == null || !_gameTiming.IsFirstTimePredicted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var filter = Filter.Pvs(uid, entityManager: EntityManager);
|
||||||
|
|
||||||
var filter = Filter.Pvs(uid);
|
if (excluded != null && _netMan.IsServer)
|
||||||
|
|
||||||
if (excluded != null)
|
|
||||||
filter = filter.RemoveWhereAttachedEntity(entity => entity == excluded.Value);
|
filter = filter.RemoveWhereAttachedEntity(entity => entity == excluded.Value);
|
||||||
|
|
||||||
SoundSystem.Play(sound.GetSound(), filter, uid, audioParams);
|
SoundSystem.Play(sound.GetSound(), filter, uid, audioParams);
|
||||||
@@ -516,7 +531,7 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (args.TryEject && slot.HasItem)
|
if (args.TryEject && slot.HasItem)
|
||||||
TryEjectToHands(uid, slot, args.Session.AttachedEntity);
|
TryEjectToHands(uid, slot, args.Session.AttachedEntity, false);
|
||||||
else if (args.TryInsert && !slot.HasItem && args.Session.AttachedEntity is EntityUid user)
|
else if (args.TryInsert && !slot.HasItem && args.Session.AttachedEntity is EntityUid user)
|
||||||
TryInsertFromHand(uid, slot, user);
|
TryInsertFromHand(uid, slot, user);
|
||||||
}
|
}
|
||||||
@@ -583,9 +598,25 @@ namespace Content.Shared.Containers.ItemSlots
|
|||||||
if (args.Current is not ItemSlotsComponentState state)
|
if (args.Current is not ItemSlotsComponentState state)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var (id, locked) in state.SlotLocked)
|
foreach (var (key, slot) in component.Slots)
|
||||||
{
|
{
|
||||||
component.Slots[id].Locked = locked;
|
if (!state.Slots.ContainsKey(key))
|
||||||
|
RemoveItemSlot(uid, slot, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (serverKey, serverSlot) in state.Slots)
|
||||||
|
{
|
||||||
|
if (component.Slots.TryGetValue(serverKey, out var itemSlot))
|
||||||
|
{
|
||||||
|
itemSlot.CopyFrom(serverSlot);
|
||||||
|
itemSlot.ContainerSlot = _containers.EnsureContainer<ContainerSlot>(uid, serverKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var slot = new ItemSlot(serverSlot);
|
||||||
|
slot.Local = false;
|
||||||
|
AddItemSlot(uid, serverKey, slot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user