]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Predict DevourSystem. (#38970)
authorKyle Tyo <36606155+VerinSenpai@users.noreply.github.com>
Wed, 16 Jul 2025 00:21:18 +0000 (20:21 -0400)
committerGitHub <noreply@github.com>
Wed, 16 Jul 2025 00:21:18 +0000 (02:21 +0200)
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Content.Client/Devour/DevourSystem.cs [deleted file]
Content.Server/Devour/DevourSystem.cs [deleted file]
Content.Shared/Devour/Components/DevourerComponent.cs
Content.Shared/Devour/DevourSystem.cs [new file with mode: 0644]
Content.Shared/Devour/SharedDevourSystem.cs [deleted file]

diff --git a/Content.Client/Devour/DevourSystem.cs b/Content.Client/Devour/DevourSystem.cs
deleted file mode 100644 (file)
index ad905ff..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-using Content.Shared.Devour;
-
-namespace Content.Client.Devour;
-public sealed class DevourSystem : SharedDevourSystem
-{
-}
diff --git a/Content.Server/Devour/DevourSystem.cs b/Content.Server/Devour/DevourSystem.cs
deleted file mode 100644 (file)
index ede13fa..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-using Content.Server.Body.Systems;
-using Content.Shared.Body.Events;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Devour;
-using Content.Shared.Devour.Components;
-using Content.Shared.Whitelist;
-
-namespace Content.Server.Devour;
-
-public sealed class DevourSystem : SharedDevourSystem
-{
-    [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
-    [Dependency] private readonly EntityWhitelistSystem _entityWhitelistSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<DevourerComponent, DevourDoAfterEvent>(OnDoAfter);
-        SubscribeLocalEvent<DevourerComponent, BeingGibbedEvent>(OnGibContents);
-    }
-
-    private void OnDoAfter(EntityUid uid, DevourerComponent component, DevourDoAfterEvent args)
-    {
-        if (args.Handled || args.Cancelled)
-            return;
-
-        var ichorInjection = new Solution(component.Chemical, component.HealRate);
-
-        // Grant ichor if the devoured thing meets the dragon's food preference
-        if (args.Args.Target != null && _entityWhitelistSystem.IsWhitelistPassOrNull(component.FoodPreferenceWhitelist, (EntityUid)args.Args.Target))
-        {
-            _bloodstreamSystem.TryAddToChemicals(uid, ichorInjection);
-        }
-
-        // If the devoured thing meets the stomach whitelist criteria, add it to the stomach
-        if (args.Args.Target != null && _entityWhitelistSystem.IsWhitelistPass(component.StomachStorageWhitelist, (EntityUid)args.Args.Target))
-        {
-            ContainerSystem.Insert(args.Args.Target.Value, component.Stomach);
-        }
-        //TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow.
-        //If it's not alive, it must be a structure.
-        // Delete if the thing isn't in the stomach storage whitelist (or the stomach whitelist is null/empty)
-        else if (args.Args.Target != null)
-        {
-            QueueDel(args.Args.Target.Value);
-        }
-
-        _audioSystem.PlayPvs(component.SoundDevour, uid);
-    }
-
-    private void OnGibContents(EntityUid uid, DevourerComponent component, ref BeingGibbedEvent args)
-    {
-        if (component.StomachStorageWhitelist == null)
-            return;
-
-        // For some reason we have two different systems that should handle gibbing,
-        // and for some another reason GibbingSystem, which should empty all containers, doesn't get involved in this process
-        ContainerSystem.EmptyContainer(component.Stomach);
-    }
-}
-
index d1101759517196c0f891abd760dd3b1c6b276b7f..857e99062c2cfddbbe50b694682a320f342bddc3 100644 (file)
@@ -4,53 +4,79 @@ using Robust.Shared.Audio;
 using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
 namespace Content.Shared.Devour.Components;
 
-[RegisterComponent, NetworkedComponent]
-[Access(typeof(SharedDevourSystem))]
+/// <summary>
+/// Allows an entity to eat whitelisted entities via an action.
+/// Eaten mobs will be stored inside a container and released when the devourer is gibbed.
+/// Eating something that fits their food preference will reward the devourer by being injected with a specific reagent.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(DevourSystem))]
 public sealed partial class DevourerComponent : Component
 {
-    [DataField(customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string? DevourAction = "ActionDevour";
-
+    /// <summary>
+    /// Action prototype for devouring.
+    /// </summary>
     [DataField]
-    public EntityUid? DevourActionEntity;
+    public EntProtoId DevourAction = "ActionDevour";
 
-    [DataField]
-    public SoundSpecifier? SoundDevour = new SoundPathSpecifier("/Audio/Effects/demon_consume.ogg")
-    {
-        Params = AudioParams.Default.WithVolume(-3f),
-    };
+    /// <summary>
+    /// The spawned action entity for devouring.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityUid? DevourActionEntity;
 
-    [DataField]
+    /// <summary>
+    /// The amount of time it takes to devour a mob.
+    /// <remarks>
+    [DataField, AutoNetworkedField]
     public float DevourTime = 3f;
 
     /// <summary>
-    /// The amount of time it takes to devour something
+    /// The amount of time it takes to devour a structure.
     /// <remarks>
     /// NOTE: original intended design was to increase this proportionally with damage thresholds, but those proved quite difficult to get consistently. right now it devours the structure at a fixed timer.
     /// </remarks>
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public float StructureDevourTime = 10f;
 
-    [DataField]
+    /// <summary>
+    /// The sound to play when finishing devouring something.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public SoundSpecifier? SoundDevour = new SoundPathSpecifier("/Audio/Effects/demon_consume.ogg")
+    {
+        Params = AudioParams.Default.WithVolume(-3f),
+    };
+
+    /// <summary>
+    /// The sound to play when starting to devour a structure.
+    /// </summary>
+    [DataField, AutoNetworkedField]
     public SoundSpecifier? SoundStructureDevour = new SoundPathSpecifier("/Audio/Machines/airlock_creaking.ogg")
     {
         Params = AudioParams.Default.WithVolume(-3f),
     };
 
+    /// <summary>
+    /// The container to store the eaten entities in.
+    /// </summary>
+    [ViewVariables]
+    public static string StomachContainerId = "stomach";
+
     /// <summary>
     /// Where the entities go when it devours them, empties when it is butchered.
     /// </summary>
+    [ViewVariables]
     public Container Stomach = default!;
 
     /// <summary>
     /// Determines what things the devourer can consume.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public EntityWhitelist? Whitelist = new()
     {
         Components = new[]
@@ -63,26 +89,26 @@ public sealed partial class DevourerComponent : Component
     /// Determines what things end up in the dragon's stomach if they eat it.
     /// If it isn't in the whitelist, it's deleted.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public EntityWhitelist? StomachStorageWhitelist;
 
     /// <summary>
-    /// Determine's the dragon's food preference.  If the eaten thing matches,
-    /// it is rewarded with the reward chemical.  If null, all food is fine.
+    /// Determine's the dragon's food preference. If the eaten thing matches,
+    /// it is rewarded with the reward chemical. If null, all food is fine.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public EntityWhitelist? FoodPreferenceWhitelist;
 
     /// <summary>
-    /// The chemical ID injected upon devouring
+    /// The chemical ID injected upon devouring.
     /// </summary>
-    [DataField(customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
-    public string Chemical = "Ichor";
+    [DataField, AutoNetworkedField]
+    public ProtoId<ReagentPrototype> Chemical = "Ichor";
 
     /// <summary>
-    /// The amount of ichor injected per devour
+    /// The amount of solution injected per devour.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public float HealRate = 15f;
 
 }
diff --git a/Content.Shared/Devour/DevourSystem.cs b/Content.Shared/Devour/DevourSystem.cs
new file mode 100644 (file)
index 0000000..71b1bd6
--- /dev/null
@@ -0,0 +1,145 @@
+using Content.Shared.Actions;
+using Content.Shared.Body.Events;
+using Content.Shared.Body.Systems;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Devour.Components;
+using Content.Shared.DoAfter;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Popups;
+using Content.Shared.Whitelist;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Devour;
+
+public sealed class DevourSystem : EntitySystem
+{
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+    [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+    [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+    [Dependency] private readonly SharedBloodstreamSystem _bloodstreamSystem = default!;
+    [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DevourerComponent, ComponentStartup>(OnStartup);
+        SubscribeLocalEvent<DevourerComponent, MapInitEvent>(OnInit);
+        SubscribeLocalEvent<DevourerComponent, ComponentShutdown>(OnShutdown);
+        SubscribeLocalEvent<DevourerComponent, DevourActionEvent>(OnDevourAction);
+        SubscribeLocalEvent<DevourerComponent, DevourDoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<DevourerComponent, BeingGibbedEvent>(OnGibContents);
+    }
+
+    private void OnStartup(Entity<DevourerComponent> ent, ref ComponentStartup args)
+    {
+        //Devourer doesn't actually chew, since he sends targets right into his stomach.
+        //I did it mom, I added ERP content into upstream. Legally!
+        ent.Comp.Stomach = _containerSystem.EnsureContainer<Container>(ent.Owner, DevourerComponent.StomachContainerId);
+    }
+
+    private void OnInit(Entity<DevourerComponent> ent, ref MapInitEvent args)
+    {
+        _actionsSystem.AddAction(ent.Owner, ref ent.Comp.DevourActionEntity, ent.Comp.DevourAction);
+    }
+
+    private void OnShutdown(Entity<DevourerComponent> ent, ref ComponentShutdown args)
+    {
+        _actionsSystem.RemoveAction(ent.Owner, ent.Comp.DevourActionEntity);
+    }
+
+    /// <summary>
+    /// The devour action
+    /// </summary>
+    private void OnDevourAction(Entity<DevourerComponent> ent, ref DevourActionEvent args)
+    {
+        if (args.Handled || _whitelistSystem.IsWhitelistFailOrNull(ent.Comp.Whitelist, args.Target))
+            return;
+
+        args.Handled = true;
+        var target = args.Target;
+
+        // Structure and mob devours handled differently.
+        if (TryComp(target, out MobStateComponent? targetState))
+        {
+            switch (targetState.CurrentState)
+            {
+                case MobState.Critical:
+                case MobState.Dead:
+
+                    _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, ent.Owner, ent.Comp.DevourTime, new DevourDoAfterEvent(), ent.Owner, target: target, used: ent.Owner)
+                    {
+                        BreakOnMove = true,
+                    });
+                    break;
+                case MobState.Invalid:
+                case MobState.Alive:
+                default:
+                    _popupSystem.PopupClient(Loc.GetString("devour-action-popup-message-fail-target-alive"), ent.Owner, ent.Owner);
+                    break;
+            }
+
+            return;
+        }
+
+        _popupSystem.PopupClient(Loc.GetString("devour-action-popup-message-structure"), ent.Owner, ent.Owner);
+
+        if (ent.Comp.SoundStructureDevour != null)
+            _audioSystem.PlayPredicted(ent.Comp.SoundStructureDevour, ent.Owner, ent.Owner, ent.Comp.SoundStructureDevour.Params);
+
+        _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, ent.Owner, ent.Comp.StructureDevourTime, new DevourDoAfterEvent(), ent.Owner, target: target, used: ent.Owner)
+        {
+            BreakOnMove = true,
+        });
+    }
+
+    private void OnDoAfter(Entity<DevourerComponent> ent, ref DevourDoAfterEvent args)
+    {
+        if (args.Handled || args.Cancelled)
+            return;
+
+        var ichorInjection = new Solution(ent.Comp.Chemical, ent.Comp.HealRate);
+
+        // Grant ichor if the devoured thing meets the dragon's food preference
+        if (args.Args.Target != null && _whitelistSystem.IsWhitelistPassOrNull(ent.Comp.FoodPreferenceWhitelist, (EntityUid)args.Args.Target))
+        {
+            _bloodstreamSystem.TryAddToChemicals(ent.Owner, ichorInjection);
+        }
+
+        // If the devoured thing meets the stomach whitelist criteria, add it to the stomach
+        if (args.Args.Target != null && _whitelistSystem.IsWhitelistPass(ent.Comp.StomachStorageWhitelist, (EntityUid)args.Args.Target))
+        {
+            _containerSystem.Insert(args.Args.Target.Value, ent.Comp.Stomach);
+        }
+        //TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow.
+        //If it's not alive, it must be a structure.
+        // Delete if the thing isn't in the stomach storage whitelist (or the stomach whitelist is null/empty)
+        else if (args.Args.Target != null)
+        {
+            PredictedQueueDel(args.Args.Target.Value);
+        }
+
+        _audioSystem.PlayPredicted(ent.Comp.SoundDevour, ent.Owner, ent.Owner);
+    }
+
+    private void OnGibContents(Entity<DevourerComponent> ent, ref BeingGibbedEvent args)
+    {
+        if (ent.Comp.StomachStorageWhitelist == null)
+            return;
+
+        // For some reason we have two different systems that should handle gibbing,
+        // and for some another reason GibbingSystem, which should empty all containers, doesn't get involved in this process
+        _containerSystem.EmptyContainer(ent.Comp.Stomach);
+    }
+}
+
+public sealed partial class DevourActionEvent : EntityTargetActionEvent;
+
+[Serializable, NetSerializable]
+public sealed partial class DevourDoAfterEvent : SimpleDoAfterEvent;
+
diff --git a/Content.Shared/Devour/SharedDevourSystem.cs b/Content.Shared/Devour/SharedDevourSystem.cs
deleted file mode 100644 (file)
index 702884c..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.Devour.Components;
-using Content.Shared.DoAfter;
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Popups;
-using Content.Shared.Whitelist;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Containers;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Devour;
-
-public abstract class SharedDevourSystem : EntitySystem
-{
-    [Dependency] protected readonly SharedAudioSystem _audioSystem = default!;
-    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
-    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
-    [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
-    [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
-    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<DevourerComponent, MapInitEvent>(OnInit);
-        SubscribeLocalEvent<DevourerComponent, DevourActionEvent>(OnDevourAction);
-    }
-
-    protected void OnInit(EntityUid uid, DevourerComponent component, MapInitEvent args)
-    {
-        //Devourer doesn't actually chew, since he sends targets right into his stomach.
-        //I did it mom, I added ERP content into upstream. Legally!
-        component.Stomach = ContainerSystem.EnsureContainer<Container>(uid, "stomach");
-
-        _actionsSystem.AddAction(uid, ref component.DevourActionEntity, component.DevourAction);
-    }
-
-    /// <summary>
-    /// The devour action
-    /// </summary>
-    protected void OnDevourAction(EntityUid uid, DevourerComponent component, DevourActionEvent args)
-    {
-        if (args.Handled || _whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, args.Target))
-            return;
-
-        args.Handled = true;
-        var target = args.Target;
-
-        // Structure and mob devours handled differently.
-        if (TryComp(target, out MobStateComponent? targetState))
-        {
-            switch (targetState.CurrentState)
-            {
-                case MobState.Critical:
-                case MobState.Dead:
-
-                    _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, uid, component.DevourTime, new DevourDoAfterEvent(), uid, target: target, used: uid)
-                    {
-                        BreakOnMove = true,
-                    });
-                    break;
-                default:
-                    _popupSystem.PopupClient(Loc.GetString("devour-action-popup-message-fail-target-alive"), uid,uid);
-                    break;
-            }
-
-            return;
-        }
-
-        _popupSystem.PopupClient(Loc.GetString("devour-action-popup-message-structure"), uid, uid);
-
-        if (component.SoundStructureDevour != null)
-            _audioSystem.PlayPredicted(component.SoundStructureDevour, uid, uid, component.SoundStructureDevour.Params);
-
-        _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, uid, component.StructureDevourTime, new DevourDoAfterEvent(), uid, target: target, used: uid)
-        {
-            BreakOnMove = true,
-        });
-    }
-}
-
-public sealed partial class DevourActionEvent : EntityTargetActionEvent { }
-
-[Serializable, NetSerializable]
-public sealed partial class DevourDoAfterEvent : SimpleDoAfterEvent { }
-