]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Predict artifact crushers (#40180)
authorslarticodefast <161409025+slarticodefast@users.noreply.github.com>
Fri, 10 Oct 2025 23:48:08 +0000 (01:48 +0200)
committerGitHub <noreply@github.com>
Fri, 10 Oct 2025 23:48:08 +0000 (23:48 +0000)
predict artifact crushers

Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs
Content.Shared/Xenoarchaeology/Equipment/Components/ArtifactCrusherComponent.cs
Content.Shared/Xenoarchaeology/Equipment/SharedArtifactCrusherSystem.cs

index 885186f79c176b18a1b4f517f3a8311034303add..05bb2327e6d593f819ff6a69250172c0b90f2acb 100644 (file)
@@ -1,87 +1,25 @@
 using Content.Server.Body.Systems;
-using Content.Server.Popups;
-using Content.Server.Power.EntitySystems;
 using Content.Server.Stack;
 using Content.Shared.Body.Components;
-using Content.Shared.Damage;
-using Content.Shared.Power;
 using Content.Shared.Storage.Components;
-using Content.Shared.Verbs;
 using Content.Shared.Whitelist;
 using Content.Shared.Xenoarchaeology.Equipment;
 using Content.Shared.Xenoarchaeology.Equipment.Components;
 using Robust.Shared.Collections;
 using Robust.Shared.Random;
-using Robust.Shared.Timing;
 
 namespace Content.Server.Xenoarchaeology.Equipment.Systems;
 
 /// <inheritdoc/>
 public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
 {
-    [Dependency] private readonly IGameTiming _timing = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly BodySystem _body = default!;
-    [Dependency] private readonly DamageableSystem _damageable = default!;
     [Dependency] private readonly StackSystem _stack = default!;
-    [Dependency] private readonly PopupSystem _popup = default!;
     [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
 
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ArtifactCrusherComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
-        SubscribeLocalEvent<ArtifactCrusherComponent, PowerChangedEvent>(OnPowerChanged);
-    }
-
-    private void OnGetVerbs(Entity<ArtifactCrusherComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
-    {
-        if (!args.CanAccess || !args.CanInteract || args.Hands == null || ent.Comp.Crushing)
-            return;
-
-        if (!TryComp<EntityStorageComponent>(ent, out var entityStorageComp) ||
-            entityStorageComp.Contents.ContainedEntities.Count == 0)
-            return;
-
-        if (!this.IsPowered(ent, EntityManager))
-            return;
-
-        var verb = new AlternativeVerb
-        {
-            Text = Loc.GetString("artifact-crusher-verb-start-crushing"),
-            Priority = 2,
-            Act = () => StartCrushing((ent, ent.Comp, entityStorageComp))
-        };
-        args.Verbs.Add(verb);
-    }
-
-    private void OnPowerChanged(Entity<ArtifactCrusherComponent> ent, ref PowerChangedEvent args)
-    {
-        if (!args.Powered)
-            StopCrushing(ent);
-    }
-
-    public void StartCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent)
-    {
-        var (uid, crusher, _) = ent;
-
-        if (crusher.Crushing)
-            return;
-
-        if (crusher.AutoLock)
-            _popup.PopupEntity(Loc.GetString("artifact-crusher-autolocks-enable"), uid);
-
-        crusher.Crushing = true;
-        crusher.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(1);
-        crusher.CrushEndTime = _timing.CurTime + crusher.CrushDuration;
-        crusher.CrushingSoundEntity = AudioSystem.PlayPvs(crusher.CrushingSound, ent);
-        Appearance.SetData(ent, ArtifactCrusherVisuals.Crushing, true);
-        Dirty(ent, ent.Comp1);
-    }
-
-    public void FinishCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent)
+    // TODO: Move to shared once StackSystem spawning is in Shared and we have RandomPredicted
+    public override void FinishCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent)
     {
         var (_, crusher, storage) = ent;
         StopCrushing((ent, ent.Comp1), false);
@@ -113,32 +51,4 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
             }
         }
     }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        var query = EntityQueryEnumerator<ArtifactCrusherComponent, EntityStorageComponent>();
-        while (query.MoveNext(out var uid, out var crusher, out var storage))
-        {
-            if (!crusher.Crushing)
-                continue;
-
-            if (crusher.NextSecond < _timing.CurTime)
-            {
-                var contents = new ValueList<EntityUid>(storage.Contents.ContainedEntities);
-                foreach (var contained in contents)
-                {
-                    _damageable.TryChangeDamage(contained, crusher.CrushingDamage);
-                }
-                crusher.NextSecond += TimeSpan.FromSeconds(1);
-                Dirty(uid, crusher);
-            }
-
-            if (crusher.CrushEndTime < _timing.CurTime)
-            {
-                FinishCrushing((uid, crusher, storage));
-            }
-        }
-    }
 }
index 69a682b6602087385f9ffbafc0f0f3353b09ca97..1130d9f305f37e71a0d666948ee7c3e9638cc608 100644 (file)
@@ -2,7 +2,6 @@ using Content.Shared.Damage;
 using Content.Shared.Stacks;
 using Content.Shared.Whitelist;
 using Robust.Shared.Audio;
-using Robust.Shared.Audio.Components;
 using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
@@ -14,7 +13,8 @@ namespace Content.Shared.Xenoarchaeology.Equipment.Components;
 /// <summary>
 /// This is an entity storage that, when activated, crushes the artifact inside of it and gives artifact fragments.
 /// </summary>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
 [Access(typeof(SharedArtifactCrusherSystem))]
 public sealed partial class ArtifactCrusherComponent : Component
 {
@@ -27,19 +27,21 @@ public sealed partial class ArtifactCrusherComponent : Component
     /// <summary>
     /// When the current crushing will end.
     /// </summary>
-    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField]
     public TimeSpan CrushEndTime;
 
     /// <summary>
     /// The next second. Used to apply damage over time.
     /// </summary>
-    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField]
     public TimeSpan NextSecond;
 
     /// <summary>
     /// The total duration of the crushing.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public TimeSpan CrushDuration = TimeSpan.FromSeconds(10);
 
     /// <summary>
@@ -51,19 +53,19 @@ public sealed partial class ArtifactCrusherComponent : Component
     /// <summary>
     /// The minimum amount of fragments spawned.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public int MinFragments = 2;
 
     /// <summary>
     /// The maximum amount of fragments spawned, non-inclusive.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public int MaxFragments = 5;
 
     /// <summary>
     /// The material for the fragments.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public ProtoId<StackPrototype> FragmentStackProtoId = "ArtifactFragment";
 
     /// <summary>
@@ -100,12 +102,12 @@ public sealed partial class ArtifactCrusherComponent : Component
     /// Stores entity of <see cref="CrushingSound"/> to allow ending it early.
     /// </summary>
     [DataField]
-    public (EntityUid, AudioComponent)? CrushingSoundEntity;
+    public EntityUid? CrushingSoundEntity;
 
     /// <summary>
     /// When enabled, stops the artifact crusher from being opened when it is being crushed.
     /// </summary>
-    [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField, AutoNetworkedField]
     public bool AutoLock = false;
 }
 
index 64f31262977336834395823c0ec99ce934a34eb5..a8c4b9ff06487c6fb12a3f4e607ac7928866730a 100644 (file)
@@ -1,9 +1,16 @@
+using Content.Shared.Damage;
+using Content.Shared.Emag.Systems;
 using Content.Shared.Examine;
+using Content.Shared.Popups;
+using Content.Shared.Power;
+using Content.Shared.Power.EntitySystems;
 using Content.Shared.Storage.Components;
+using Content.Shared.Verbs;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
 using Robust.Shared.Audio.Systems;
+using Robust.Shared.Collections;
 using Robust.Shared.Containers;
-using Content.Shared.Emag.Systems;
-using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Shared.Timing;
 
 namespace Content.Shared.Xenoarchaeology.Equipment;
 
@@ -12,10 +19,14 @@ namespace Content.Shared.Xenoarchaeology.Equipment;
 /// </summary>
 public abstract class SharedArtifactCrusherSystem : EntitySystem
 {
-    [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
     [Dependency] protected readonly SharedAudioSystem AudioSystem = default!;
     [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
     [Dependency] private readonly EmagSystem _emag = default!;
+    [Dependency] private readonly SharedPowerReceiverSystem _power = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly DamageableSystem _damageable = default!;
 
     /// <inheritdoc/>
     public override void Initialize()
@@ -27,6 +38,8 @@ public abstract class SharedArtifactCrusherSystem : EntitySystem
         SubscribeLocalEvent<ArtifactCrusherComponent, StorageOpenAttemptEvent>(OnStorageOpenAttempt);
         SubscribeLocalEvent<ArtifactCrusherComponent, ExaminedEvent>(OnExamine);
         SubscribeLocalEvent<ArtifactCrusherComponent, GotEmaggedEvent>(OnEmagged);
+        SubscribeLocalEvent<ArtifactCrusherComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
+        SubscribeLocalEvent<ArtifactCrusherComponent, PowerChangedEvent>(OnPowerChanged);
     }
 
     private void OnInit(Entity<ArtifactCrusherComponent> ent, ref ComponentInit args)
@@ -53,6 +66,7 @@ public abstract class SharedArtifactCrusherSystem : EntitySystem
 
         ent.Comp.AutoLock = true;
         args.Handled = true;
+        Dirty(ent);
     }
 
     private void OnStorageOpenAttempt(Entity<ArtifactCrusherComponent> ent, ref StorageOpenAttemptEvent args)
@@ -66,22 +80,94 @@ public abstract class SharedArtifactCrusherSystem : EntitySystem
         args.PushMarkup(ent.Comp.AutoLock ? Loc.GetString("artifact-crusher-examine-autolocks") : Loc.GetString("artifact-crusher-examine-no-autolocks"));
     }
 
-    public void StopCrushing(Entity<ArtifactCrusherComponent> ent, bool early = true)
+    private void OnGetVerbs(Entity<ArtifactCrusherComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+    {
+        if (!args.CanAccess || !args.CanInteract || args.Hands == null || ent.Comp.Crushing)
+            return;
+
+        if (!TryComp<EntityStorageComponent>(ent, out var entityStorageComp) ||
+            entityStorageComp.Contents.ContainedEntities.Count == 0)
+            return;
+
+        if (!_power.IsPowered(ent.Owner))
+            return;
+
+        var user = args.User;
+        var verb = new AlternativeVerb
+        {
+            Text = Loc.GetString("artifact-crusher-verb-start-crushing"),
+            Priority = 2,
+            Act = () => StartCrushing((ent, ent.Comp, entityStorageComp), user)
+        };
+        args.Verbs.Add(verb);
+    }
+
+    private void OnPowerChanged(Entity<ArtifactCrusherComponent> ent, ref PowerChangedEvent args)
+    {
+        if (!args.Powered)
+            StopCrushing(ent);
+    }
+
+    public void StartCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent, EntityUid? user = null)
     {
-        var (_, crusher) = ent;
+        var (uid, crusher, _) = ent;
+
+        if (crusher.Crushing)
+            return;
 
-        if (!crusher.Crushing)
+        if (crusher.AutoLock)
+            _popup.PopupPredicted(Loc.GetString("artifact-crusher-autolocks-enable"), uid, user);
+
+        crusher.Crushing = true;
+        crusher.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(1);
+        crusher.CrushEndTime = _timing.CurTime + crusher.CrushDuration;
+        crusher.CrushingSoundEntity = AudioSystem.PlayPvs(crusher.CrushingSound, ent)?.Entity;
+        _appearance.SetData(ent, ArtifactCrusherVisuals.Crushing, true);
+        Dirty(ent, ent.Comp1);
+    }
+
+    public void StopCrushing(Entity<ArtifactCrusherComponent> ent, bool early = true)
+    {
+        if (!ent.Comp.Crushing)
             return;
 
-        crusher.Crushing = false;
-        Appearance.SetData(ent, ArtifactCrusherVisuals.Crushing, false);
+        ent.Comp.Crushing = false;
+        _appearance.SetData(ent, ArtifactCrusherVisuals.Crushing, false);
 
         if (early)
         {
-            AudioSystem.Stop(crusher.CrushingSoundEntity?.Item1, crusher.CrushingSoundEntity?.Item2);
-            crusher.CrushingSoundEntity = null;
+            AudioSystem.Stop(ent.Comp.CrushingSoundEntity);
+            ent.Comp.CrushingSoundEntity = null;
         }
 
         Dirty(ent, ent.Comp);
     }
+
+    public virtual void FinishCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent) { }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var query = EntityQueryEnumerator<ArtifactCrusherComponent, EntityStorageComponent>();
+        while (query.MoveNext(out var uid, out var crusher, out var storage))
+        {
+            if (!crusher.Crushing)
+                continue;
+
+            if (crusher.NextSecond < _timing.CurTime)
+            {
+                var contents = new ValueList<EntityUid>(storage.Contents.ContainedEntities);
+                foreach (var contained in contents)
+                {
+                    _damageable.TryChangeDamage(contained, crusher.CrushingDamage);
+                }
+                crusher.NextSecond += TimeSpan.FromSeconds(1);
+                Dirty(uid, crusher);
+            }
+
+            if (crusher.CrushEndTime < _timing.CurTime)
+                FinishCrushing((uid, crusher, storage));
+        }
+    }
 }