Predict helmet toggles (#22089)
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using Content.Shared.Clothing.EntitySystems;
|
using Content.Shared.Clothing.EntitySystems;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
namespace Content.Shared.Clothing.Components;
|
namespace Content.Shared.Clothing.Components;
|
||||||
|
|
||||||
@@ -9,12 +10,12 @@ namespace Content.Shared.Clothing.Components;
|
|||||||
/// hardsuit helmets.
|
/// hardsuit helmets.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Access(typeof(ToggleableClothingSystem))]
|
[Access(typeof(ToggleableClothingSystem))]
|
||||||
[RegisterComponent]
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||||
public sealed partial class AttachedClothingComponent : Component
|
public sealed partial class AttachedClothingComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Id of the piece of clothing that this entity belongs to.
|
/// The Id of the piece of clothing that this entity belongs to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("AttachedUid")]
|
[DataField, AutoNetworkedField]
|
||||||
public EntityUid AttachedUid = default!;
|
public EntityUid AttachedUid;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
using Content.Shared.Clothing.EntitySystems;
|
using Content.Shared.Clothing.EntitySystems;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
namespace Content.Shared.Clothing.Components;
|
namespace Content.Shared.Clothing.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This component gives an item an action that will equip or un-equip some clothing. Intended for use with
|
/// This component gives an item an action that will equip or un-equip some clothing e.g. hardsuits and hardsuit helmets.
|
||||||
/// hardsuits and hardsuit helmets.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Access(typeof(ToggleableClothingSystem))]
|
[Access(typeof(ToggleableClothingSystem))]
|
||||||
[RegisterComponent]
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||||
public sealed partial class ToggleableClothingComponent : Component
|
public sealed partial class ToggleableClothingComponent : Component
|
||||||
{
|
{
|
||||||
public const string DefaultClothingContainerId = "toggleable-clothing";
|
public const string DefaultClothingContainerId = "toggleable-clothing";
|
||||||
@@ -19,35 +19,35 @@ public sealed partial class ToggleableClothingComponent : Component
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Action used to toggle the clothing on or off.
|
/// Action used to toggle the clothing on or off.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
[DataField, AutoNetworkedField]
|
||||||
public string Action = "ActionToggleSuitPiece";
|
public EntProtoId Action = "ActionToggleSuitPiece";
|
||||||
|
|
||||||
[DataField("actionEntity")]
|
[DataField, AutoNetworkedField]
|
||||||
public EntityUid? ActionEntity;
|
public EntityUid? ActionEntity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default clothing entity prototype to spawn into the clothing container.
|
/// Default clothing entity prototype to spawn into the clothing container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("clothingPrototype", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
[DataField(required: true), AutoNetworkedField]
|
||||||
public string ClothingPrototype = default!;
|
public EntProtoId ClothingPrototype = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The inventory slot that the clothing is equipped to.
|
/// The inventory slot that the clothing is equipped to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("slot")]
|
[DataField, AutoNetworkedField]
|
||||||
public string Slot = "head";
|
public string Slot = "head";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The inventory slot flags required for this component to function.
|
/// The inventory slot flags required for this component to function.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("requiredSlot")]
|
[DataField("requiredSlot"), AutoNetworkedField]
|
||||||
public SlotFlags RequiredFlags = SlotFlags.OUTERCLOTHING;
|
public SlotFlags RequiredFlags = SlotFlags.OUTERCLOTHING;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The container that the clothing is stored in when not equipped.
|
/// The container that the clothing is stored in when not equipped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("containerId")]
|
[DataField, AutoNetworkedField]
|
||||||
public string ContainerId = DefaultClothingContainerId;
|
public string ContainerId = DefaultClothingContainerId;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
@@ -57,18 +57,18 @@ public sealed partial class ToggleableClothingComponent : Component
|
|||||||
/// The Id of the piece of clothing that belongs to this component. Required for map-saving if the clothing is
|
/// The Id of the piece of clothing that belongs to this component. Required for map-saving if the clothing is
|
||||||
/// currently not inside of the container.
|
/// currently not inside of the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("clothingUid")]
|
[DataField, AutoNetworkedField]
|
||||||
public EntityUid? ClothingUid;
|
public EntityUid? ClothingUid;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time it takes for this clothing to be toggled via the stripping menu verbs. Null prevents the verb from even showing up.
|
/// Time it takes for this clothing to be toggled via the stripping menu verbs. Null prevents the verb from even showing up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("stripDelay")]
|
[DataField, AutoNetworkedField]
|
||||||
public TimeSpan? StripDelay = TimeSpan.FromSeconds(3);
|
public TimeSpan? StripDelay = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text shown in the toggle-clothing verb. Defaults to using the name of the <see cref="ActionEntity"/> action.
|
/// Text shown in the toggle-clothing verb. Defaults to using the name of the <see cref="ActionEntity"/> action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("verbText")]
|
[DataField, AutoNetworkedField]
|
||||||
public string? VerbText;
|
public string? VerbText;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ using Content.Shared.Popups;
|
|||||||
using Content.Shared.Strip;
|
using Content.Shared.Strip;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Clothing.EntitySystems;
|
namespace Content.Shared.Clothing.EntitySystems;
|
||||||
|
|
||||||
public sealed class ToggleableClothingSystem : EntitySystem
|
public sealed class ToggleableClothingSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly INetManager _netMan = default!;
|
||||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||||
@@ -24,8 +28,6 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
|||||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||||
[Dependency] private readonly SharedStrippableSystem _strippable = default!;
|
[Dependency] private readonly SharedStrippableSystem _strippable = default!;
|
||||||
|
|
||||||
private Queue<EntityUid> _toInsert = new();
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -128,18 +130,6 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
|||||||
ToggleClothing(args.User, uid, component);
|
ToggleClothing(args.User, uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
base.Update(frameTime);
|
|
||||||
|
|
||||||
// process delayed insertions. Avoids doing a container insert during a container removal.
|
|
||||||
while (_toInsert.TryDequeue(out var uid))
|
|
||||||
{
|
|
||||||
if (TryComp(uid, out ToggleableClothingComponent? component) && component.ClothingUid != null)
|
|
||||||
component.Container?.Insert(component.ClothingUid.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnInteractHand(EntityUid uid, AttachedClothingComponent component, InteractHandEvent args)
|
private void OnInteractHand(EntityUid uid, AttachedClothingComponent component, InteractHandEvent args)
|
||||||
{
|
{
|
||||||
if (args.Handled)
|
if (args.Handled)
|
||||||
@@ -182,7 +172,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
|||||||
_actionsSystem.RemoveAction(action.AttachedEntity.Value, component.ActionEntity);
|
_actionsSystem.RemoveAction(action.AttachedEntity.Value, component.ActionEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.ClothingUid != null)
|
if (component.ClothingUid != null && !_netMan.IsClient)
|
||||||
QueueDel(component.ClothingUid.Value);
|
QueueDel(component.ClothingUid.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +204,10 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnAttachedUnequip(EntityUid uid, AttachedClothingComponent component, GotUnequippedEvent args)
|
private void OnAttachedUnequip(EntityUid uid, AttachedClothingComponent component, GotUnequippedEvent args)
|
||||||
{
|
{
|
||||||
|
// Let containers worry about it.
|
||||||
|
if (_timing.ApplyingState)
|
||||||
|
return;
|
||||||
|
|
||||||
if (component.LifeStage > ComponentLifeStage.Running)
|
if (component.LifeStage > ComponentLifeStage.Running)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -225,7 +219,8 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
|||||||
|
|
||||||
// As unequipped gets called in the middle of container removal, we cannot call a container-insert without causing issues.
|
// As unequipped gets called in the middle of container removal, we cannot call a container-insert without causing issues.
|
||||||
// So we delay it and process it during a system update:
|
// So we delay it and process it during a system update:
|
||||||
_toInsert.Enqueue(component.AttachedUid);
|
if (toggleComp.ClothingUid != null)
|
||||||
|
toggleComp.Container?.Insert(toggleComp.ClothingUid.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -250,7 +245,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
|||||||
_inventorySystem.TryUnequip(user, parent, component.Slot);
|
_inventorySystem.TryUnequip(user, parent, component.Slot);
|
||||||
else if (_inventorySystem.TryGetSlotEntity(parent, component.Slot, out var existing))
|
else if (_inventorySystem.TryGetSlotEntity(parent, component.Slot, out var existing))
|
||||||
{
|
{
|
||||||
_popupSystem.PopupEntity(Loc.GetString("toggleable-clothing-remove-first", ("entity", existing)),
|
_popupSystem.PopupClient(Loc.GetString("toggleable-clothing-remove-first", ("entity", existing)),
|
||||||
user, user);
|
user, user);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -294,8 +289,11 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
var xform = Transform(uid);
|
var xform = Transform(uid);
|
||||||
component.ClothingUid = Spawn(component.ClothingPrototype, xform.Coordinates);
|
component.ClothingUid = Spawn(component.ClothingPrototype, xform.Coordinates);
|
||||||
EnsureComp<AttachedClothingComponent>(component.ClothingUid.Value).AttachedUid = uid;
|
var attachedClothing = EnsureComp<AttachedClothingComponent>(component.ClothingUid.Value);
|
||||||
|
attachedClothing.AttachedUid = uid;
|
||||||
|
Dirty(component.ClothingUid.Value, attachedClothing);
|
||||||
component.Container.Insert(component.ClothingUid.Value, EntityManager, ownerTransform: xform);
|
component.Container.Insert(component.ClothingUid.Value, EntityManager, ownerTransform: xform);
|
||||||
|
Dirty(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_actionContainer.EnsureAction(uid, ref component.ActionEntity, out var action, component.Action))
|
if (_actionContainer.EnsureAction(uid, ref component.ActionEntity, out var action, component.Action))
|
||||||
|
|||||||
@@ -198,21 +198,8 @@ public abstract partial class InventorySystem
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!silent && clothing != null && clothing.EquipSound != null && _gameTiming.IsFirstTimePredicted)
|
if (!silent && clothing != null && clothing.EquipSound != null)
|
||||||
{
|
{
|
||||||
Filter filter;
|
|
||||||
|
|
||||||
if (_netMan.IsClient)
|
|
||||||
filter = Filter.Local();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
filter = Filter.Pvs(target);
|
|
||||||
|
|
||||||
// don't play double audio for predicted interactions
|
|
||||||
if (predicted)
|
|
||||||
filter.RemoveWhereAttachedEntity(entity => entity == actor);
|
|
||||||
}
|
|
||||||
|
|
||||||
_audio.PlayPredicted(clothing.EquipSound, target, actor);
|
_audio.PlayPredicted(clothing.EquipSound, target, actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,23 +372,13 @@ public abstract partial class InventorySystem
|
|||||||
if (!slotContainer.Remove(removedItem.Value, force: force))
|
if (!slotContainer.Remove(removedItem.Value, force: force))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
_transform.DropNextTo(removedItem.Value, target);
|
// TODO: Inventory needs a hot cleanup hoo boy
|
||||||
|
// Check if something else (AKA toggleable) dumped it into a container.
|
||||||
|
if (!_containerSystem.IsEntityInContainer(removedItem.Value))
|
||||||
|
_transform.DropNextTo(removedItem.Value, target);
|
||||||
|
|
||||||
if (!silent && Resolve(removedItem.Value, ref clothing, false) && clothing.UnequipSound != null && _gameTiming.IsFirstTimePredicted)
|
if (!silent && Resolve(removedItem.Value, ref clothing, false) && clothing.UnequipSound != null)
|
||||||
{
|
{
|
||||||
Filter filter;
|
|
||||||
|
|
||||||
if (_netMan.IsClient)
|
|
||||||
filter = Filter.Local();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
filter = Filter.Pvs(target);
|
|
||||||
|
|
||||||
// don't play double audio for predicted interactions
|
|
||||||
if (predicted)
|
|
||||||
filter.RemoveWhereAttachedEntity(entity => entity == actor);
|
|
||||||
}
|
|
||||||
|
|
||||||
_audio.PlayPredicted(clothing.UnequipSound, target, actor);
|
_audio.PlayPredicted(clothing.UnequipSound, target, actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user