using Content.Shared.Examine; using Content.Shared.Rejuvenate; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Timing; using Robust.Shared.Network; using Robust.Shared.Prototypes; namespace Content.Shared.Emp; public abstract class SharedEmpSystem : EntitySystem { [Dependency] protected readonly IGameTiming Timing = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; private HashSet _entSet = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnRemove); SubscribeLocalEvent(OnRejuvenate); } public static readonly EntProtoId EmpPulseEffectPrototype = "EffectEmpPulse"; public static readonly EntProtoId EmpDisabledEffectPrototype = "EffectEmpDisabled"; public static readonly SoundSpecifier EmpSound = new SoundPathSpecifier("/Audio/Effects/Lightning/lightningbolt.ogg"); /// /// Triggers an EMP pulse at the given location, by first raising an , then by raising on all entities in range. /// /// The location to trigger the EMP pulse at. /// The range of the EMP pulse. /// The amount of energy consumed by the EMP pulse. In Joule. /// The duration of the EMP effects. /// The player that caused the effect. Used for predicted audio. public void EmpPulse(MapCoordinates mapCoordinates, float range, float energyConsumption, TimeSpan duration, EntityUid? user = null) { foreach (var uid in _lookup.GetEntitiesInRange(mapCoordinates, range)) { TryEmpEffects(uid, energyConsumption, duration, user); } // TODO: replace with PredictedSpawn once it works with animated sprites if (_net.IsServer) Spawn(EmpPulseEffectPrototype, mapCoordinates); var coordinates = _transform.ToCoordinates(mapCoordinates); _audio.PlayPredicted(EmpSound, coordinates, user); } /// /// Triggers an EMP pulse at the given location, by first raising an , then a raising on all entities in range. /// /// The location to trigger the EMP pulse at. /// The range of the EMP pulse. /// The amount of energy consumed by the EMP pulse. /// The duration of the EMP effects. /// The player that caused the effect. Used for predicted audio. public void EmpPulse(EntityCoordinates coordinates, float range, float energyConsumption, TimeSpan duration, EntityUid? user = null) { _entSet.Clear(); _lookup.GetEntitiesInRange(coordinates, range, _entSet); foreach (var uid in _entSet) { TryEmpEffects(uid, energyConsumption, duration, user); } // TODO: replace with PredictedSpawn once it works with animated sprites if (_net.IsServer) Spawn(EmpPulseEffectPrototype, coordinates); _audio.PlayPredicted(EmpSound, coordinates, user); } /// /// Attempts to apply the effects of an EMP pulse onto an entity by first raising an , followed by raising a on it. /// /// The entity to apply the EMP effects on. /// The amount of energy consumed by the EMP. /// The duration of the EMP effects. /// The player that caused the EMP. For prediction purposes. /// If the entity was affected by the EMP. public bool TryEmpEffects(EntityUid uid, float energyConsumption, TimeSpan duration, EntityUid? user = null) { var attemptEv = new EmpAttemptEvent(); RaiseLocalEvent(uid, ref attemptEv); if (attemptEv.Cancelled) return false; return DoEmpEffects(uid, energyConsumption, duration, user); } /// /// Applies the effects of an EMP pulse onto an entity by raising a on it. /// /// The entity to apply the EMP effects on. /// The amount of energy consumed by the EMP. /// The duration of the EMP effects. /// The player that caused the EMP. For prediction purposes. /// If the entity was affected by the EMP. public bool DoEmpEffects(EntityUid uid, float energyConsumption, TimeSpan duration, EntityUid? user = null) { var ev = new EmpPulseEvent(energyConsumption, false, false, duration, user); RaiseLocalEvent(uid, ref ev); // TODO: replace with PredictedSpawn once it works with animated sprites if (ev.Affected && _net.IsServer) Spawn(EmpDisabledEffectPrototype, Transform(uid).Coordinates); if (!ev.Disabled) return ev.Affected; var disabled = EnsureComp(uid); disabled.DisabledUntil = Timing.CurTime + duration; Dirty(uid, disabled); return ev.Affected; } public override void Update(float frameTime) { base.Update(frameTime); var curTime = Timing.CurTime; var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp)) { if (curTime < comp.DisabledUntil) continue; RemComp(uid); } } private void OnExamine(Entity ent, ref ExaminedEvent args) { args.PushMarkup(Loc.GetString("emp-disabled-comp-on-examine")); } private void OnRemove(Entity ent, ref ComponentRemove args) { var ev = new EmpDisabledRemovedEvent(); RaiseLocalEvent(ent, ref ev); } private void OnRejuvenate(Entity ent, ref RejuvenateEvent args) { RemCompDeferred(ent); } } /// /// Raised on an entity before . Cancel this to prevent the emp event being raised. /// [ByRefEvent] public record struct EmpAttemptEvent(bool Cancelled); /// /// Raised on an entity when it gets hit by an EMP Pulse. /// /// The amount of energy to remove from batteries. In Joule. /// Set this is true in the subscription to spawn a visual effect at the entity's location. /// Set this to ture in the subscription to add to the entity. /// The duration the entity will be disabled. /// The player that caused the EMP. For prediction purposes. [ByRefEvent] public record struct EmpPulseEvent(float EnergyConsumption, bool Affected, bool Disabled, TimeSpan Duration, EntityUid? User); /// /// Raised on an entity after is removed. /// [ByRefEvent] public record struct EmpDisabledRemovedEvent();