]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Predict DestructibleSystem Part 2: First batch of entity effects (#41039)
authorslarticodefast <161409025+slarticodefast@users.noreply.github.com>
Thu, 23 Oct 2025 10:33:30 +0000 (12:33 +0200)
committerGitHub <noreply@github.com>
Thu, 23 Oct 2025 10:33:30 +0000 (10:33 +0000)
* first batch

* fix name

* fix

Content.Server/EntityEffects/Effects/ExplodeEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/Tiles/TileEntityEffectSystem.cs
Content.Shared/Destructible/SharedDestructibleSystem.cs
Content.Shared/EntityEffects/Effects/ExplodeEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/MetaData/DestructibleActEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Transform/ExplosionEntityEffect.cs
Content.Shared/EntityEffects/Effects/Transform/PopupMessageEntityEffectSystem.cs
Content.Shared/EntityEffects/EntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/EntityEffectEvent.cs [new file with mode: 0644]
Content.Shared/EntityEffects/SharedEntityEffectsSystem.cs
Resources/Locale/en-US/guidebook/entity-effects/effects.ftl

diff --git a/Content.Server/EntityEffects/Effects/ExplodeEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/ExplodeEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..497fec3
--- /dev/null
@@ -0,0 +1,20 @@
+using Content.Server.Explosion.EntitySystems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Transform;
+using Content.Shared.Explosion.Components;
+
+namespace Content.Server.EntityEffects.Effects;
+
+/// <summary>
+/// Makes this entity explode using its <see cref="ExplosiveComponent"/>.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ExplodeEntityEffectSystem : EntityEffectSystem<ExplosiveComponent, ExplodeEffect>
+{
+    [Dependency] private readonly ExplosionSystem _explosion = default!;
+
+    protected override void Effect(Entity<ExplosiveComponent> entity, ref EntityEffectEvent<ExplodeEffect> args)
+    {
+        _explosion.TriggerExplosive(entity, entity, args.Effect.Delete, args.Effect.Intensity, args.Effect.Radius, args.User);
+    }
+}
index bd4aa789c2fef255851e27fbb143fda0ad287fcb..1123781e7b4d869cb276c33b3ce082ebc4cbbbf1 100644 (file)
@@ -22,6 +22,6 @@ public sealed class TileEntityEffectSystem : EntitySystem
     {
         var otherUid = args.Tripper;
 
-        _entityEffects.ApplyEffects(otherUid, ent.Comp.Effects.ToArray());
+        _entityEffects.ApplyEffects(otherUid, ent.Comp.Effects.ToArray(), user: otherUid);
     }
 }
index f37561e0d8ff66b32366836d8af0d61bccb6d84a..6b7148a4c9e74ffc8da1884e97422fe86cdffff5 100644 (file)
@@ -5,7 +5,7 @@ public abstract class SharedDestructibleSystem : EntitySystem
     /// <summary>
     /// Force entity to be destroyed and deleted.
     /// </summary>
-    public bool DestroyEntity(EntityUid owner)
+    public bool DestroyEntity(Entity<MetaDataComponent?> owner)
     {
         var ev = new DestructionAttemptEvent();
         RaiseLocalEvent(owner, ev);
diff --git a/Content.Shared/EntityEffects/Effects/ExplodeEntityEffect.cs b/Content.Shared/EntityEffects/Effects/ExplodeEntityEffect.cs
new file mode 100644 (file)
index 0000000..0a99ef9
--- /dev/null
@@ -0,0 +1,32 @@
+using Content.Shared.Database;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Transform;
+
+/// <inheritdoc cref="EntityEffect"/>
+/// <seealso cref="ExplosionEffect">
+public sealed partial class ExplodeEffect : EntityEffectBase<ExplodeEffect>
+{
+    /// <summary>
+    /// Optional override for the explosion intensity.
+    /// </summary>
+    [DataField]
+    public float? Intensity;
+
+    /// <summary>
+    /// Optional override for the explosion radius.
+    /// </summary>
+    [DataField]
+    public float? Radius;
+
+    /// <summary>
+    /// Delete the entity with the explosion?
+    /// </summary>
+    [DataField]
+    public bool Delete = true;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-explosion", ("chance", Probability));
+
+    public override LogImpact? Impact => LogImpact.High;
+}
diff --git a/Content.Shared/EntityEffects/Effects/MetaData/DestructibleActEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/MetaData/DestructibleActEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..05fd826
--- /dev/null
@@ -0,0 +1,40 @@
+using Content.Shared.Destructible;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.MetaData;
+
+
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class DestructibleActEntityEffectSystem : EntityEffectSystem<MetaDataComponent, DestructibleAct>
+{
+    [Dependency] private readonly SharedDestructibleSystem _destructible = default!;
+
+    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<DestructibleAct> args)
+    {
+        if ((args.Effect.Acts & ThresholdActs.Breakage) != 0)
+            _destructible.BreakEntity(entity);
+
+        if ((args.Effect.Acts & ThresholdActs.Destruction) != 0)
+            _destructible.DestroyEntity(entity.AsNullable());
+    }
+}
+
+/// <summary>
+/// Destroys or breaks an entity.
+/// </summary>
+public sealed partial class DestructibleAct : EntityEffectBase<DestructibleAct>
+{
+    /// <summary>
+    /// What acts should be triggered upon activation.
+    /// </summary>
+    [DataField]
+    public ThresholdActs Acts;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+    {
+        if ((Acts & ThresholdActs.Destruction) != 0)
+            return Loc.GetString("entity-effect-guidebook-destroy", ("chance", Probability));
+
+        return Loc.GetString("entity-effect-guidebook-break", ("chance", Probability));
+    }
+}
index 95fd98294cf0598fa77f08a221a061184fabf073..3cb4d3aac060ba7b392f61634a19a9f8ca53fd5d 100644 (file)
@@ -5,6 +5,7 @@ using Robust.Shared.Prototypes;
 namespace Content.Shared.EntityEffects.Effects.Transform;
 
 /// <inheritdoc cref="EntityEffect"/>
+/// <seealso cref="ExplodeEffect">
 public sealed partial class ExplosionEffect : EntityEffectBase<ExplosionEffect>
 {
     /// <summary>
@@ -50,7 +51,7 @@ public sealed partial class ExplosionEffect : EntityEffectBase<ExplosionEffect>
     public float TileBreakScale = 1f;
 
     public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("entity-effect-guidebook-explosion-reaction-effect", ("chance", Probability));
+        => Loc.GetString("entity-effect-guidebook-explosion", ("chance", Probability));
 
     public override LogImpact? Impact => LogImpact.High;
 }
index 53d9acd2c97fec656b26212891eec6cf0bd1935b..5381d5e01ea2bc48232a9c22657bf95131aac494 100644 (file)
@@ -1,6 +1,7 @@
 using Content.Shared.Popups;
 using Robust.Shared.Network;
 using Robust.Shared.Random;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.EntityEffects.Effects.Transform;
 
@@ -20,15 +21,21 @@ public sealed partial class PopupMessageEntityEffectSystem : EntityEffectSystem<
         if (_net.IsClient)
             return;
 
-        var msg = _random.Pick(args.Effect.Messages);
+        var msg = Loc.GetString(_random.Pick(args.Effect.Messages), ("entity", entity));
 
-        switch (args.Effect.Type)
+        switch ((args.Effect.Method, args.Effect.Type))
         {
-            case PopupRecipients.Local:
-                _popup.PopupEntity(Loc.GetString(msg, ("entity", entity)), entity, entity, args.Effect.VisualType);
+            case (PopupMethod.PopupEntity, PopupRecipients.Local):
+                _popup.PopupEntity(msg, entity, entity, args.Effect.VisualType);
                 break;
-            case PopupRecipients.Pvs:
-                _popup.PopupEntity(Loc.GetString(msg, ("entity", entity)), entity, args.Effect.VisualType);
+            case (PopupMethod.PopupEntity, PopupRecipients.Pvs):
+                _popup.PopupEntity(msg, entity, args.Effect.VisualType);
+                break;
+            case (PopupMethod.PopupCoordinates, PopupRecipients.Local):
+                _popup.PopupCoordinates(msg, Transform(entity).Coordinates, entity, args.Effect.VisualType);
+                break;
+            case (PopupMethod.PopupCoordinates, PopupRecipients.Pvs):
+                _popup.PopupCoordinates(msg, Transform(entity).Coordinates, args.Effect.VisualType);
                 break;
         }
     }
@@ -50,6 +57,13 @@ public sealed partial class PopupMessage : EntityEffectBase<PopupMessage>
     [DataField]
     public PopupRecipients Type = PopupRecipients.Local;
 
+    /// <summary>
+    /// Which popup API method to use.
+    /// Use PopupCoordinates in case the entity will be deleted while the popup is shown.
+    /// </summary>
+    [DataField]
+    public PopupMethod Method = PopupMethod.PopupEntity;
+
     /// <summary>
     /// Size of the popup.
     /// </summary>
@@ -57,8 +71,16 @@ public sealed partial class PopupMessage : EntityEffectBase<PopupMessage>
     public PopupType VisualType = PopupType.Small;
 }
 
-public enum PopupRecipients
+[Serializable, NetSerializable]
+public enum PopupRecipients : byte
 {
     Pvs,
-    Local
+    Local,
+}
+
+[Serializable, NetSerializable]
+public enum PopupMethod : byte
+{
+    PopupEntity,
+    PopupCoordinates,
 }
diff --git a/Content.Shared/EntityEffects/EntityEffect.cs b/Content.Shared/EntityEffects/EntityEffect.cs
new file mode 100644 (file)
index 0000000..072ff8a
--- /dev/null
@@ -0,0 +1,62 @@
+using Content.Shared.Database;
+using Content.Shared.EntityConditions;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects;
+
+/// <summary>
+/// A basic instantaneous effect which can be applied to an entity via events.
+/// </summary>
+[ImplicitDataDefinitionForInheritors]
+public abstract partial class EntityEffect
+{
+    public abstract void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale, EntityUid? user);
+
+    [DataField]
+    public EntityCondition[]? Conditions;
+
+    /// <summary>
+    /// If our scale is less than this value, the effect fails.
+    /// </summary>
+    [DataField]
+    public virtual float MinScale { get; private set; }
+
+    /// <summary>
+    /// If true, then it allows the scale multiplier to go above 1.
+    /// </summary>
+    [DataField]
+    public virtual bool Scaling { get; private set; }
+
+    // TODO: This should be an entity condition but guidebook relies on it heavily for formatting...
+    /// <summary>
+    /// Probability of the effect occuring.
+    /// </summary>
+    [DataField]
+    public float Probability = 1.0f;
+
+    public virtual string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
+
+    /// <summary>
+    /// If this effect is logged, how important is the log?
+    /// </summary>
+    [ViewVariables]
+    public virtual LogImpact? Impact => null;
+
+    [ViewVariables]
+    public virtual LogType LogType => LogType.EntityEffect;
+}
+
+/// <summary>
+/// Used to store an <see cref="EntityEffect"/> so it can be raised without losing the type of the condition.
+/// </summary>
+/// <typeparam name="T">The Condition wer are raising.</typeparam>
+public abstract partial class EntityEffectBase<T> : EntityEffect where T : EntityEffectBase<T>
+{
+    public override void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale, EntityUid? user)
+    {
+        if (this is not T type)
+            return;
+
+        raiser.RaiseEffectEvent(target, type, scale, user);
+    }
+}
diff --git a/Content.Shared/EntityEffects/EntityEffectEvent.cs b/Content.Shared/EntityEffects/EntityEffectEvent.cs
new file mode 100644 (file)
index 0000000..aa9baad
--- /dev/null
@@ -0,0 +1,27 @@
+namespace Content.Shared.EntityEffects;
+
+/// <summary>
+/// An Event carrying an entity effect.
+/// </summary>
+/// <param name="Effect">The Effect</param>
+/// <param name="Scale">A strength scalar for the effect, defaults to 1 and typically only goes under for incomplete reactions.</param>
+/// <param name="User">The entity causing the effect.</param>
+[ByRefEvent, Access(typeof(SharedEntityEffectsSystem))]
+public readonly record struct EntityEffectEvent<T>(T Effect, float Scale, EntityUid? User) where T : EntityEffectBase<T>
+{
+    /// <summary>
+    /// The Condition being raised in this event
+    /// </summary>
+    public readonly T Effect = Effect;
+
+    /// <summary>
+    /// The Scale modifier of this Effect.
+    /// </summary>
+    public readonly float Scale = Scale;
+
+    /// <summary>
+    /// The entity that caused this effect.
+    /// Used for admin logs and prediction purposes.
+    /// </summary>
+    public readonly EntityUid? User = User;
+}
index 91360b3a84f3090c8a71bbe7548e1be687363244..7eed94a0996db16ddd72c30758a53aa6d88d863e 100644 (file)
@@ -1,11 +1,8 @@
-using System.Diagnostics.CodeAnalysis;
 using Content.Shared.Administration.Logs;
 using Content.Shared.Chemistry;
 using Content.Shared.Chemistry.Reaction;
-using Content.Shared.Database;
 using Content.Shared.EntityConditions;
 using Content.Shared.Random.Helpers;
-using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 
@@ -67,12 +64,13 @@ public sealed partial class SharedEntityEffectsSystem : EntitySystem, IEntityEff
     /// <param name="target">Entity being targeted by the effects</param>
     /// <param name="effects">Effects we're applying to the entity</param>
     /// <param name="scale">Optional scale multiplier for the effects</param>
-    public void ApplyEffects(EntityUid target, EntityEffect[] effects, float scale = 1f)
+    /// <param name="user">The entity causing the effect.</param>
+    public void ApplyEffects(EntityUid target, EntityEffect[] effects, float scale = 1f, EntityUid? user = null)
     {
         // do all effects, if conditions apply
         foreach (var effect in effects)
         {
-            TryApplyEffect(target, effect, scale);
+            TryApplyEffect(target, effect, scale, user);
         }
     }
 
@@ -81,9 +79,10 @@ public sealed partial class SharedEntityEffectsSystem : EntitySystem, IEntityEff
     /// </summary>
     /// <param name="target">Target we're applying an effect to</param>
     /// <param name="effect">Effect we're applying</param>
-    /// <param name="scale">Optional scale multiplier for the effect. Not all </param>
+    /// <param name="scale">Optional scale multiplier for the effect.</param>
+    /// <param name="user">The entity causing the effect.</param>
     /// <returns>True if all conditions pass!</returns>
-    public bool TryApplyEffect(EntityUid target, EntityEffect effect, float scale = 1f)
+    public bool TryApplyEffect(EntityUid target, EntityEffect effect, float scale = 1f, EntityUid? user = null)
     {
         if (scale < effect.MinScale)
             return false;
@@ -101,7 +100,7 @@ public sealed partial class SharedEntityEffectsSystem : EntitySystem, IEntityEff
         if (!_condition.TryConditions(target, effect.Conditions))
             return false;
 
-        ApplyEffect(target, effect, scale);
+        ApplyEffect(target, effect, scale, user);
         return true;
     }
 
@@ -111,14 +110,15 @@ public sealed partial class SharedEntityEffectsSystem : EntitySystem, IEntityEff
     /// </summary>
     /// <param name="target">Target we're applying an effect to</param>
     /// <param name="effect">Effect we're applying</param>
-    /// <param name="scale">Optional scale multiplier for the effect. Not all </param>
-    public void ApplyEffect(EntityUid target, EntityEffect effect, float scale = 1f)
+    /// <param name="scale">Optional scale multiplier for the effect.</param>
+    /// <param name="user">The entity causing the effect.</param>
+    public void ApplyEffect(EntityUid target, EntityEffect effect, float scale = 1f, EntityUid? user = null)
     {
         // Clamp the scale if the effect doesn't allow scaling.
         if (!effect.Scaling)
             scale = Math.Min(scale, 1f);
 
-        if (effect.Impact is {} level)
+        if (effect.Impact is { } level)
         {
             _adminLog.Add(
                 effect.LogType,
@@ -130,15 +130,15 @@ public sealed partial class SharedEntityEffectsSystem : EntitySystem, IEntityEff
             );
         }
 
-        effect.RaiseEvent(target, this, scale);
+        effect.RaiseEvent(target, this, scale, user);
     }
 
     /// <summary>
     /// Raises an effect to an entity. You should not be calling this unless you know what you're doing.
     /// </summary>
-    public void RaiseEffectEvent<T>(EntityUid target, T effect, float scale) where T : EntityEffectBase<T>
+    public void RaiseEffectEvent<T>(EntityUid target, T effect, float scale, EntityUid? user) where T : EntityEffectBase<T>
     {
-        var effectEv = new EntityEffectEvent<T>(effect, scale);
+        var effectEv = new EntityEffectEvent<T>(effect, scale, user);
         RaiseLocalEvent(target, ref effectEv);
     }
 }
@@ -164,81 +164,5 @@ public abstract partial class EntityEffectSystem<T, TEffect> : EntitySystem wher
 /// </summary>
 public interface IEntityEffectRaiser
 {
-    void RaiseEffectEvent<T>(EntityUid target, T effect, float scale) where T : EntityEffectBase<T>;
-}
-
-/// <summary>
-/// Used to store an <see cref="EntityEffect"/> so it can be raised without losing the type of the condition.
-/// </summary>
-/// <typeparam name="T">The Condition wer are raising.</typeparam>
-public abstract partial class EntityEffectBase<T> : EntityEffect where T : EntityEffectBase<T>
-{
-    public override void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale)
-    {
-        if (this is not T type)
-            return;
-
-        raiser.RaiseEffectEvent(target, type, scale);
-    }
-}
-
-/// <summary>
-/// A basic instantaneous effect which can be applied to an entity via events.
-/// </summary>
-[ImplicitDataDefinitionForInheritors]
-public abstract partial class EntityEffect
-{
-    public abstract void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale);
-
-    [DataField]
-    public EntityCondition[]? Conditions;
-
-    /// <summary>
-    /// If our scale is less than this value, the effect fails.
-    /// </summary>
-    [DataField]
-    public virtual float MinScale { get; private set; }
-
-    /// <summary>
-    /// If true, then it allows the scale multiplier to go above 1.
-    /// </summary>
-    [DataField]
-    public virtual bool Scaling { get; private set; }
-
-    // TODO: This should be an entity condition but guidebook relies on it heavily for formatting...
-    /// <summary>
-    /// Probability of the effect occuring.
-    /// </summary>
-    [DataField]
-    public float Probability = 1.0f;
-
-    public virtual string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
-
-    /// <summary>
-    /// If this effect is logged, how important is the log?
-    /// </summary>
-    [ViewVariables]
-    public virtual LogImpact? Impact => null;
-
-    [ViewVariables]
-    public virtual LogType LogType => LogType.EntityEffect;
-}
-
-/// <summary>
-/// An Event carrying an entity effect.
-/// </summary>
-/// <param name="Effect">The Effect</param>
-/// <param name="Scale">A strength scalar for the effect, defaults to 1 and typically only goes under for incomplete reactions.</param>
-[ByRefEvent, Access(typeof(SharedEntityEffectsSystem))]
-public readonly record struct EntityEffectEvent<T>(T Effect, float Scale) where T : EntityEffectBase<T>
-{
-    /// <summary>
-    /// The Condition being raised in this event
-    /// </summary>
-    public readonly T Effect = Effect;
-
-    /// <summary>
-    /// The Scale modifier of this Effect.
-    /// </summary>
-    public readonly float Scale = Scale;
+    void RaiseEffectEvent<T>(EntityUid target, T effect, float scale, EntityUid? user) where T : EntityEffectBase<T>;
 }
index 0de71133caec5c3a33b7ed8fecc1da26a5c83512..bbfaca7ee91ac5d38e4c9e47157bb78efce52bde 100644 (file)
@@ -25,6 +25,18 @@ entity-effect-guidebook-spawn-entity =
         *[other] {$amount} {MAKEPLURAL($entname)}
     }
 
+entity-effect-guidebook-destroy =
+    { $chance ->
+        [1] Destroys
+        *[other] destroy
+    } the object
+
+entity-effect-guidebook-break =
+    { $chance ->
+        [1] Breaks
+        *[other] break
+    } the object
+
 entity-effect-guidebook-explosion =
     { $chance ->
         [1] Causes