]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Predict GlueSystem (#39079)
authorKyle Tyo <36606155+VerinSenpai@users.noreply.github.com>
Wed, 30 Jul 2025 19:57:50 +0000 (15:57 -0400)
committerGitHub <noreply@github.com>
Wed, 30 Jul 2025 19:57:50 +0000 (12:57 -0700)
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Content.Shared/Glue/GlueComponent.cs
Content.Shared/Glue/GlueSystem.cs [moved from Content.Server/Glue/GlueSystem.cs with 76% similarity]
Content.Shared/Glue/GluedComponent.cs
Content.Shared/Glue/SharedGlueSystem.cs [deleted file]
Content.Shared/Interaction/Components/UnremoveableComponent.cs

index 4cbbc497379bb26aa07a153bf4a8e0c4768c0113..6f68b3fbca9c4af65fb24707b42d22205b941a42 100644 (file)
@@ -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))]
+/// <summary>
+/// This component indicates that an item is glue and can be used as such.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(GlueSystem))]
 public sealed partial class GlueComponent : Component
 {
     /// <summary>
     /// Noise made when glue applied.
     /// </summary>
-    [DataField("squeeze")]
+    [DataField, AutoNetworkedField]
     public SoundSpecifier Squeeze = new SoundPathSpecifier("/Audio/Items/squeezebottle.ogg");
 
     /// <summary>
     /// Solution on the entity that contains the glue.
     /// </summary>
-    [DataField("solution")]
+    [DataField, AutoNetworkedField]
     public string Solution = "drink";
 
     /// <summary>
     /// Reagent that will be used as glue.
     /// </summary>
-    [DataField("reagent", customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
-    public string Reagent = "SpaceGlue";
+    [DataField, AutoNetworkedField]
+    public ProtoId<ReagentPrototype> Reagent = "SpaceGlue";
 
     /// <summary>
     /// Reagent consumption per use.
     /// </summary>
-    [DataField("consumptionUnit"), ViewVariables(VVAccess.ReadWrite)]
+    [DataField, AutoNetworkedField]
     public FixedPoint2 ConsumptionUnit = FixedPoint2.New(5);
 
     /// <summary>
     /// Duration per unit
     /// </summary>
-    [DataField("durationPerUnit"), ViewVariables(VVAccess.ReadWrite)]
+    [DataField, AutoNetworkedField]
     public TimeSpan DurationPerUnit = TimeSpan.FromSeconds(6);
 }
similarity index 76%
rename from Content.Server/Glue/GlueSystem.cs
rename to Content.Shared/Glue/GlueSystem.cs
index d8f8e687d27fa44e5e42bdde2945fb6ed177178b..9782b48b2da9a3c5349d7e73e52163b4384b4dd5 100644 (file)
@@ -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<GluedComponent>(target) || !HasComp<ItemComponent>(target) || HasComp<UnremoveableComponent>(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<ItemComponent>(target) && _solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out _, out var solution))
+        if (HasComp<ItemComponent>(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<GluedComponent>(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<GluedComponent>(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<GluedComponent> 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<UnremoveableComponent>(entity);
         comp.DeleteOnDrop = false;
         entity.Comp.Until = _timing.CurTime + entity.Comp.Duration;
+        Dirty(entity.Owner, comp);
+        Dirty(entity);
     }
 
     private void OnRefreshNameModifiers(Entity<GluedComponent> entity, ref RefreshNameModifiersEvent args)
index 4b46f0aa5b0ef4ea7a10fcb424785d7119e14b3c..1ef77c81937bf7f920bae37ba3e9304707bf3c7c 100644 (file)
@@ -1,15 +1,26 @@
+using Robust.Shared.GameStates;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
 namespace Content.Shared.Glue;
 
-[RegisterComponent]
-[Access(typeof(SharedGlueSystem))]
+/// <summary>
+/// This component gets attached to an item that has been glued.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+[Access(typeof(GlueSystem))]
 public sealed partial class GluedComponent : Component
 {
-
-    [DataField("until", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+    /// <summary>
+    /// The TimeSpan this effect expires at.
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField]
     public TimeSpan Until;
 
-    [DataField("duration", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+    /// <summary>
+    /// The duration this effect will last. Determined by the quantity of the reagent that is applied.
+    /// </summary>
+    [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 (file)
index d8b413a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Content.Shared.Glue;
-
-public abstract class SharedGlueSystem : EntitySystem
-{
-}
index cd10694865800faac86c40773bc5612ad39dc349..a3606f3d2ca1f60fbd780b43bf611f78d06f2b74 100644 (file)
@@ -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
-    {
-        /// <summary>
-        /// 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.
-        /// </summary>
-        [DataField("deleteOnDrop")]
-        public bool DeleteOnDrop = true;
-    }
+    /// <summary>
+    /// 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.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool DeleteOnDrop = true;
 }