diff --git a/Content.Shared/Glue/GlueComponent.cs b/Content.Shared/Glue/GlueComponent.cs
index 4cbbc49737..6f68b3fbca 100644
--- a/Content.Shared/Glue/GlueComponent.cs
+++ b/Content.Shared/Glue/GlueComponent.cs
@@ -2,41 +2,44 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
namespace Content.Shared.Glue;
-[RegisterComponent, NetworkedComponent]
-[Access(typeof(SharedGlueSystem))]
+///
+/// This component indicates that an item is glue and can be used as such.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(GlueSystem))]
public sealed partial class GlueComponent : Component
{
///
/// Noise made when glue applied.
///
- [DataField("squeeze")]
+ [DataField, AutoNetworkedField]
public SoundSpecifier Squeeze = new SoundPathSpecifier("/Audio/Items/squeezebottle.ogg");
///
/// Solution on the entity that contains the glue.
///
- [DataField("solution")]
+ [DataField, AutoNetworkedField]
public string Solution = "drink";
///
/// Reagent that will be used as glue.
///
- [DataField("reagent", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string Reagent = "SpaceGlue";
+ [DataField, AutoNetworkedField]
+ public ProtoId Reagent = "SpaceGlue";
///
/// Reagent consumption per use.
///
- [DataField("consumptionUnit"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField, AutoNetworkedField]
public FixedPoint2 ConsumptionUnit = FixedPoint2.New(5);
///
/// Duration per unit
///
- [DataField("durationPerUnit"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField, AutoNetworkedField]
public TimeSpan DurationPerUnit = TimeSpan.FromSeconds(6);
}
diff --git a/Content.Server/Glue/GlueSystem.cs b/Content.Shared/Glue/GlueSystem.cs
similarity index 76%
rename from Content.Server/Glue/GlueSystem.cs
rename to Content.Shared/Glue/GlueSystem.cs
index d8f8e687d2..9782b48b2d 100644
--- a/Content.Server/Glue/GlueSystem.cs
+++ b/Content.Shared/Glue/GlueSystem.cs
@@ -1,7 +1,6 @@
-using Content.Server.Administration.Logs;
+using Content.Shared.Administration.Logs;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Database;
-using Content.Shared.Glue;
using Content.Shared.Hands;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
@@ -13,17 +12,17 @@ using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;
-namespace Content.Server.Glue;
+namespace Content.Shared.Glue;
-public sealed class GlueSystem : SharedGlueSystem
+public sealed class GlueSystem : EntitySystem
{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly NameModifierSystem _nameMod = default!;
+ [Dependency] private readonly OpenableSystem _openable = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IAdminLogManager _adminLogger = default!;
- [Dependency] private readonly OpenableSystem _openable = default!;
- [Dependency] private readonly NameModifierSystem _nameMod = default!;
public override void Initialize()
{
@@ -62,7 +61,7 @@ public sealed class GlueSystem : SharedGlueSystem
Act = () => TryGlue(entity, target, user),
IconEntity = GetNetEntity(entity),
Text = Loc.GetString("glue-verb-text"),
- Message = Loc.GetString("glue-verb-message")
+ Message = Loc.GetString("glue-verb-message"),
};
args.Verbs.Add(verb);
@@ -72,26 +71,29 @@ public sealed class GlueSystem : SharedGlueSystem
{
// if item is glued then don't apply glue again so it can be removed for reasonable time
// If glue is applied to an unremoveable item, the component will disappear after the duration.
- // This effecitvely means any unremoveable item could be removed with a bottle of glue.
+ // This effectively means any unremoveable item could be removed with a bottle of glue.
if (HasComp(target) || !HasComp(target) || HasComp(target))
{
- _popup.PopupEntity(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
+ _popup.PopupClient(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
return false;
}
- if (HasComp(target) && _solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out _, out var solution))
+ if (HasComp(target) && _solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out var solutionEntity, out _))
{
- var quantity = solution.RemoveReagent(entity.Comp.Reagent, entity.Comp.ConsumptionUnit);
+ var quantity = _solutionContainer.RemoveReagent(solutionEntity.Value, entity.Comp.Reagent, entity.Comp.ConsumptionUnit);
if (quantity > 0)
{
- EnsureComp(target).Duration = quantity.Double() * entity.Comp.DurationPerUnit;
+ _audio.PlayPredicted(entity.Comp.Squeeze, entity.Owner, actor);
+ _popup.PopupClient(Loc.GetString("glue-success", ("target", target)), actor, actor, PopupType.Medium);
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(actor):actor} glued {ToPrettyString(target):subject} with {ToPrettyString(entity.Owner):tool}");
- _audio.PlayPvs(entity.Comp.Squeeze, entity.Owner);
- _popup.PopupEntity(Loc.GetString("glue-success", ("target", target)), actor, actor, PopupType.Medium);
+ var gluedComp = EnsureComp(target);
+ gluedComp.Duration = quantity.Double() * entity.Comp.DurationPerUnit;
+ Dirty(target, gluedComp);
return true;
}
}
- _popup.PopupEntity(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
+
+ _popup.PopupClient(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
return false;
}
@@ -119,9 +121,16 @@ public sealed class GlueSystem : SharedGlueSystem
private void OnHandPickUp(Entity entity, ref GotEquippedHandEvent args)
{
+ // When predicting dropping a glued item prediction will reinsert the item into the hand when rerolling the state to a previous one.
+ // So dropping the item would add UnRemoveableComponent on the client without this guard statement.
+ if (_timing.ApplyingState)
+ return;
+
var comp = EnsureComp(entity);
comp.DeleteOnDrop = false;
entity.Comp.Until = _timing.CurTime + entity.Comp.Duration;
+ Dirty(entity.Owner, comp);
+ Dirty(entity);
}
private void OnRefreshNameModifiers(Entity entity, ref RefreshNameModifiersEvent args)
diff --git a/Content.Shared/Glue/GluedComponent.cs b/Content.Shared/Glue/GluedComponent.cs
index 4b46f0aa5b..1ef77c8193 100644
--- a/Content.Shared/Glue/GluedComponent.cs
+++ b/Content.Shared/Glue/GluedComponent.cs
@@ -1,15 +1,26 @@
+using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Glue;
-[RegisterComponent]
-[Access(typeof(SharedGlueSystem))]
+///
+/// This component gets attached to an item that has been glued.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+[Access(typeof(GlueSystem))]
public sealed partial class GluedComponent : Component
{
-
- [DataField("until", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ ///
+ /// The TimeSpan this effect expires at.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
public TimeSpan Until;
- [DataField("duration", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ ///
+ /// The duration this effect will last. Determined by the quantity of the reagent that is applied.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField]
public TimeSpan Duration;
}
diff --git a/Content.Shared/Glue/SharedGlueSystem.cs b/Content.Shared/Glue/SharedGlueSystem.cs
deleted file mode 100644
index d8b413a1e1..0000000000
--- a/Content.Shared/Glue/SharedGlueSystem.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Content.Shared.Glue;
-
-public abstract class SharedGlueSystem : EntitySystem
-{
-}
diff --git a/Content.Shared/Interaction/Components/UnremoveableComponent.cs b/Content.Shared/Interaction/Components/UnremoveableComponent.cs
index cd10694865..a3606f3d2c 100644
--- a/Content.Shared/Interaction/Components/UnremoveableComponent.cs
+++ b/Content.Shared/Interaction/Components/UnremoveableComponent.cs
@@ -1,17 +1,14 @@
-using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
-namespace Content.Shared.Interaction.Components
+namespace Content.Shared.Interaction.Components;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class UnremoveableComponent : Component
{
- [RegisterComponent]
- [NetworkedComponent]
- public sealed partial class UnremoveableComponent : Component
- {
- ///
- /// If this is true then unremovable items that are removed from inventory are deleted (typically from corpse gibbing).
- /// Items within unremovable containers are not deleted when removed.
- ///
- [DataField("deleteOnDrop")]
- public bool DeleteOnDrop = true;
- }
+ ///
+ /// If this is true then unremovable items that are removed from inventory are deleted (typically from corpse gibbing).
+ /// Items within unremovable containers are not deleted when removed.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool DeleteOnDrop = true;
}