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; }