From 6159801442fd29394aab0ddeae3df6feb8b25518 Mon Sep 17 00:00:00 2001
From: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Date: Thu, 23 Oct 2025 12:33:30 +0200
Subject: [PATCH] Predict DestructibleSystem Part 2: First batch of entity
effects (#41039)
* first batch
* fix name
* fix
---
.../Effects/ExplodeEntityEffectSystem.cs | 20 ++++
.../Tiles/TileEntityEffectSystem.cs | 2 +-
.../Destructible/SharedDestructibleSystem.cs | 2 +-
.../Effects/ExplodeEntityEffect.cs | 32 ++++++
.../DestructibleActEntityEffectSystem.cs | 40 +++++++
.../Transform/ExplosionEntityEffect.cs | 3 +-
.../PopupMessageEntityEffectSystem.cs | 38 +++++--
Content.Shared/EntityEffects/EntityEffect.cs | 62 ++++++++++
.../EntityEffects/EntityEffectEvent.cs | 27 +++++
.../SharedEntityEffectsSystem.cs | 106 +++---------------
.../guidebook/entity-effects/effects.ftl | 12 ++
11 files changed, 242 insertions(+), 102 deletions(-)
create mode 100644 Content.Server/EntityEffects/Effects/ExplodeEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/ExplodeEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/MetaData/DestructibleActEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/EntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/EntityEffectEvent.cs
diff --git a/Content.Server/EntityEffects/Effects/ExplodeEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/ExplodeEntityEffectSystem.cs
new file mode 100644
index 0000000000..497fec3409
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/ExplodeEntityEffectSystem.cs
@@ -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;
+
+///
+/// Makes this entity explode using its .
+///
+///
+public sealed partial class ExplodeEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly ExplosionSystem _explosion = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ _explosion.TriggerExplosive(entity, entity, args.Effect.Delete, args.Effect.Intensity, args.Effect.Radius, args.User);
+ }
+}
diff --git a/Content.Server/Tiles/TileEntityEffectSystem.cs b/Content.Server/Tiles/TileEntityEffectSystem.cs
index bd4aa789c2..1123781e7b 100644
--- a/Content.Server/Tiles/TileEntityEffectSystem.cs
+++ b/Content.Server/Tiles/TileEntityEffectSystem.cs
@@ -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);
}
}
diff --git a/Content.Shared/Destructible/SharedDestructibleSystem.cs b/Content.Shared/Destructible/SharedDestructibleSystem.cs
index f37561e0d8..6b7148a4c9 100644
--- a/Content.Shared/Destructible/SharedDestructibleSystem.cs
+++ b/Content.Shared/Destructible/SharedDestructibleSystem.cs
@@ -5,7 +5,7 @@ public abstract class SharedDestructibleSystem : EntitySystem
///
/// Force entity to be destroyed and deleted.
///
- public bool DestroyEntity(EntityUid owner)
+ public bool DestroyEntity(Entity 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
index 0000000000..0a99ef90f1
--- /dev/null
+++ b/Content.Shared/EntityEffects/Effects/ExplodeEntityEffect.cs
@@ -0,0 +1,32 @@
+using Content.Shared.Database;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Transform;
+
+///
+///
+public sealed partial class ExplodeEffect : EntityEffectBase
+{
+ ///
+ /// Optional override for the explosion intensity.
+ ///
+ [DataField]
+ public float? Intensity;
+
+ ///
+ /// Optional override for the explosion radius.
+ ///
+ [DataField]
+ public float? Radius;
+
+ ///
+ /// Delete the entity with the explosion?
+ ///
+ [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
index 0000000000..05fd826f2d
--- /dev/null
+++ b/Content.Shared/EntityEffects/Effects/MetaData/DestructibleActEntityEffectSystem.cs
@@ -0,0 +1,40 @@
+using Content.Shared.Destructible;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.MetaData;
+
+
+///
+public sealed partial class DestructibleActEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly SharedDestructibleSystem _destructible = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if ((args.Effect.Acts & ThresholdActs.Breakage) != 0)
+ _destructible.BreakEntity(entity);
+
+ if ((args.Effect.Acts & ThresholdActs.Destruction) != 0)
+ _destructible.DestroyEntity(entity.AsNullable());
+ }
+}
+
+///
+/// Destroys or breaks an entity.
+///
+public sealed partial class DestructibleAct : EntityEffectBase
+{
+ ///
+ /// What acts should be triggered upon activation.
+ ///
+ [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));
+ }
+}
diff --git a/Content.Shared/EntityEffects/Effects/Transform/ExplosionEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Transform/ExplosionEntityEffect.cs
index 95fd98294c..3cb4d3aac0 100644
--- a/Content.Shared/EntityEffects/Effects/Transform/ExplosionEntityEffect.cs
+++ b/Content.Shared/EntityEffects/Effects/Transform/ExplosionEntityEffect.cs
@@ -5,6 +5,7 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects.Transform;
///
+///
public sealed partial class ExplosionEffect : EntityEffectBase
{
///
@@ -50,7 +51,7 @@ public sealed partial class ExplosionEffect : EntityEffectBase
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;
}
diff --git a/Content.Shared/EntityEffects/Effects/Transform/PopupMessageEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Transform/PopupMessageEntityEffectSystem.cs
index 53d9acd2c9..5381d5e01e 100644
--- a/Content.Shared/EntityEffects/Effects/Transform/PopupMessageEntityEffectSystem.cs
+++ b/Content.Shared/EntityEffects/Effects/Transform/PopupMessageEntityEffectSystem.cs
@@ -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
[DataField]
public PopupRecipients Type = PopupRecipients.Local;
+ ///
+ /// Which popup API method to use.
+ /// Use PopupCoordinates in case the entity will be deleted while the popup is shown.
+ ///
+ [DataField]
+ public PopupMethod Method = PopupMethod.PopupEntity;
+
///
/// Size of the popup.
///
@@ -57,8 +71,16 @@ public sealed partial class PopupMessage : EntityEffectBase
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
index 0000000000..072ff8a61a
--- /dev/null
+++ b/Content.Shared/EntityEffects/EntityEffect.cs
@@ -0,0 +1,62 @@
+using Content.Shared.Database;
+using Content.Shared.EntityConditions;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects;
+
+///
+/// A basic instantaneous effect which can be applied to an entity via events.
+///
+[ImplicitDataDefinitionForInheritors]
+public abstract partial class EntityEffect
+{
+ public abstract void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale, EntityUid? user);
+
+ [DataField]
+ public EntityCondition[]? Conditions;
+
+ ///
+ /// If our scale is less than this value, the effect fails.
+ ///
+ [DataField]
+ public virtual float MinScale { get; private set; }
+
+ ///
+ /// If true, then it allows the scale multiplier to go above 1.
+ ///
+ [DataField]
+ public virtual bool Scaling { get; private set; }
+
+ // TODO: This should be an entity condition but guidebook relies on it heavily for formatting...
+ ///
+ /// Probability of the effect occuring.
+ ///
+ [DataField]
+ public float Probability = 1.0f;
+
+ public virtual string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
+
+ ///
+ /// If this effect is logged, how important is the log?
+ ///
+ [ViewVariables]
+ public virtual LogImpact? Impact => null;
+
+ [ViewVariables]
+ public virtual LogType LogType => LogType.EntityEffect;
+}
+
+///
+/// Used to store an so it can be raised without losing the type of the condition.
+///
+/// The Condition wer are raising.
+public abstract partial class EntityEffectBase : EntityEffect where T : EntityEffectBase
+{
+ 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
index 0000000000..aa9baad23b
--- /dev/null
+++ b/Content.Shared/EntityEffects/EntityEffectEvent.cs
@@ -0,0 +1,27 @@
+namespace Content.Shared.EntityEffects;
+
+///
+/// An Event carrying an entity effect.
+///
+/// The Effect
+/// A strength scalar for the effect, defaults to 1 and typically only goes under for incomplete reactions.
+/// The entity causing the effect.
+[ByRefEvent, Access(typeof(SharedEntityEffectsSystem))]
+public readonly record struct EntityEffectEvent(T Effect, float Scale, EntityUid? User) where T : EntityEffectBase
+{
+ ///
+ /// The Condition being raised in this event
+ ///
+ public readonly T Effect = Effect;
+
+ ///
+ /// The Scale modifier of this Effect.
+ ///
+ public readonly float Scale = Scale;
+
+ ///
+ /// The entity that caused this effect.
+ /// Used for admin logs and prediction purposes.
+ ///
+ public readonly EntityUid? User = User;
+}
diff --git a/Content.Shared/EntityEffects/SharedEntityEffectsSystem.cs b/Content.Shared/EntityEffects/SharedEntityEffectsSystem.cs
index 91360b3a84..7eed94a099 100644
--- a/Content.Shared/EntityEffects/SharedEntityEffectsSystem.cs
+++ b/Content.Shared/EntityEffects/SharedEntityEffectsSystem.cs
@@ -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
/// Entity being targeted by the effects
/// Effects we're applying to the entity
/// Optional scale multiplier for the effects
- public void ApplyEffects(EntityUid target, EntityEffect[] effects, float scale = 1f)
+ /// The entity causing the effect.
+ 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
///
/// Target we're applying an effect to
/// Effect we're applying
- /// Optional scale multiplier for the effect. Not all
+ /// Optional scale multiplier for the effect.
+ /// The entity causing the effect.
/// True if all conditions pass!
- 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
///
/// Target we're applying an effect to
/// Effect we're applying
- /// Optional scale multiplier for the effect. Not all
- public void ApplyEffect(EntityUid target, EntityEffect effect, float scale = 1f)
+ /// Optional scale multiplier for the effect.
+ /// The entity causing the effect.
+ 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);
}
///
/// Raises an effect to an entity. You should not be calling this unless you know what you're doing.
///
- public void RaiseEffectEvent(EntityUid target, T effect, float scale) where T : EntityEffectBase
+ public void RaiseEffectEvent(EntityUid target, T effect, float scale, EntityUid? user) where T : EntityEffectBase
{
- var effectEv = new EntityEffectEvent(effect, scale);
+ var effectEv = new EntityEffectEvent(effect, scale, user);
RaiseLocalEvent(target, ref effectEv);
}
}
@@ -164,81 +164,5 @@ public abstract partial class EntityEffectSystem : EntitySystem wher
///
public interface IEntityEffectRaiser
{
- void RaiseEffectEvent(EntityUid target, T effect, float scale) where T : EntityEffectBase;
-}
-
-///
-/// Used to store an so it can be raised without losing the type of the condition.
-///
-/// The Condition wer are raising.
-public abstract partial class EntityEffectBase : EntityEffect where T : EntityEffectBase
-{
- public override void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale)
- {
- if (this is not T type)
- return;
-
- raiser.RaiseEffectEvent(target, type, scale);
- }
-}
-
-///
-/// A basic instantaneous effect which can be applied to an entity via events.
-///
-[ImplicitDataDefinitionForInheritors]
-public abstract partial class EntityEffect
-{
- public abstract void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale);
-
- [DataField]
- public EntityCondition[]? Conditions;
-
- ///
- /// If our scale is less than this value, the effect fails.
- ///
- [DataField]
- public virtual float MinScale { get; private set; }
-
- ///
- /// If true, then it allows the scale multiplier to go above 1.
- ///
- [DataField]
- public virtual bool Scaling { get; private set; }
-
- // TODO: This should be an entity condition but guidebook relies on it heavily for formatting...
- ///
- /// Probability of the effect occuring.
- ///
- [DataField]
- public float Probability = 1.0f;
-
- public virtual string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
-
- ///
- /// If this effect is logged, how important is the log?
- ///
- [ViewVariables]
- public virtual LogImpact? Impact => null;
-
- [ViewVariables]
- public virtual LogType LogType => LogType.EntityEffect;
-}
-
-///
-/// An Event carrying an entity effect.
-///
-/// The Effect
-/// A strength scalar for the effect, defaults to 1 and typically only goes under for incomplete reactions.
-[ByRefEvent, Access(typeof(SharedEntityEffectsSystem))]
-public readonly record struct EntityEffectEvent(T Effect, float Scale) where T : EntityEffectBase
-{
- ///
- /// The Condition being raised in this event
- ///
- public readonly T Effect = Effect;
-
- ///
- /// The Scale modifier of this Effect.
- ///
- public readonly float Scale = Scale;
+ void RaiseEffectEvent(EntityUid target, T effect, float scale, EntityUid? user) where T : EntityEffectBase;
}
diff --git a/Resources/Locale/en-US/guidebook/entity-effects/effects.ftl b/Resources/Locale/en-US/guidebook/entity-effects/effects.ftl
index 0de71133ca..bbfaca7ee9 100644
--- a/Resources/Locale/en-US/guidebook/entity-effects/effects.ftl
+++ b/Resources/Locale/en-US/guidebook/entity-effects/effects.ftl
@@ -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