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