using Content.Shared.Camera; using Content.Shared.Eye.Blinding.Components; using Content.Shared.Inventory; using Content.Shared.Rejuvenate; using JetBrains.Annotations; namespace Content.Shared.Eye.Blinding.Systems; public sealed class BlindableSystem : EntitySystem { [Dependency] private readonly BlurryVisionSystem _blurriness = default!; [Dependency] private readonly EyeClosingSystem _eyelids = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnRejuvenate); SubscribeLocalEvent(OnDamageChanged); SubscribeLocalEvent(OnGetEyePvsScaleAttemptEvent); SubscribeLocalEvent(OnGetEyeOffsetAttemptEvent); } private void OnRejuvenate(Entity ent, ref RejuvenateEvent args) { AdjustEyeDamage((ent.Owner, ent.Comp), -ent.Comp.EyeDamage); } private void OnDamageChanged(Entity ent, ref EyeDamageChangedEvent args) { _blurriness.UpdateBlurMagnitude((ent.Owner, ent.Comp)); _eyelids.UpdateEyesClosable((ent.Owner, ent.Comp)); } private void OnGetEyePvsScaleAttemptEvent(Entity ent, ref GetEyePvsScaleAttemptEvent args) { if (ent.Comp.IsBlind) args.Cancelled = true; } private void OnGetEyeOffsetAttemptEvent(Entity ent, ref GetEyeOffsetAttemptEvent args) { if (ent.Comp.IsBlind) args.Cancelled = true; } [PublicAPI] public void UpdateIsBlind(Entity blindable) { if (!Resolve(blindable, ref blindable.Comp, false)) return; var old = blindable.Comp.IsBlind; // Don't bother raising an event if the eye is too damaged. if (blindable.Comp.EyeDamage >= blindable.Comp.MaxDamage) { blindable.Comp.IsBlind = true; } else { var ev = new CanSeeAttemptEvent(); RaiseLocalEvent(blindable.Owner, ev); blindable.Comp.IsBlind = ev.Blind; } if (old == blindable.Comp.IsBlind) return; var changeEv = new BlindnessChangedEvent(blindable.Comp.IsBlind); RaiseLocalEvent(blindable.Owner, ref changeEv); Dirty(blindable); } public void AdjustEyeDamage(Entity blindable, int amount) { if (!Resolve(blindable, ref blindable.Comp, false) || amount == 0) return; blindable.Comp.EyeDamage += amount; UpdateEyeDamage(blindable, true); } private void UpdateEyeDamage(Entity blindable, bool isDamageChanged) { if (!Resolve(blindable, ref blindable.Comp, false)) return; var previousDamage = blindable.Comp.EyeDamage; blindable.Comp.EyeDamage = Math.Clamp(blindable.Comp.EyeDamage, blindable.Comp.MinDamage, blindable.Comp.MaxDamage); Dirty(blindable); if (!isDamageChanged && previousDamage == blindable.Comp.EyeDamage) return; UpdateIsBlind(blindable); var ev = new EyeDamageChangedEvent(blindable.Comp.EyeDamage); RaiseLocalEvent(blindable.Owner, ref ev); } public void SetMinDamage(Entity blindable, int amount) { if (!Resolve(blindable, ref blindable.Comp, false)) return; blindable.Comp.MinDamage = amount; UpdateEyeDamage(blindable, false); } } /// /// This event is raised when an entity's blindness changes /// [ByRefEvent] public record struct BlindnessChangedEvent(bool Blind); /// /// This event is raised when an entity's eye damage changes /// [ByRefEvent] public record struct EyeDamageChangedEvent(int Damage); /// /// Raised directed at an entity to see whether the entity is currently blind or not. /// public sealed class CanSeeAttemptEvent : CancellableEntityEventArgs, IInventoryRelayEvent { public bool Blind => Cancelled; public SlotFlags TargetSlots => SlotFlags.EYES | SlotFlags.MASK | SlotFlags.HEAD; } public sealed class GetEyeProtectionEvent : EntityEventArgs, IInventoryRelayEvent { /// /// Time to subtract from any temporary blindness sources. /// public TimeSpan Protection; public SlotFlags TargetSlots => SlotFlags.EYES | SlotFlags.MASK | SlotFlags.HEAD; }