using System.Numerics; using Content.Shared._CP14.MagicVision; using Content.Shared.Examine; using Content.Shared.StatusEffectNew; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Client.Timing; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Utility; namespace Content.Client._CP14.MagicVision; public sealed class CP14ClientMagicVisionSystem : CP14SharedMagicVisionSystem { [Dependency] private readonly IClientGameTiming _timing = default!; [Dependency] private readonly SpriteSystem _sprite = default!; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly IOverlayManager _overlayMan = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly StatusEffectsSystem _status = default!; private CP14MagicVisionOverlay? _overlay; private CP14MagicVisionNoirOverlay? _overlay2; private TimeSpan _nextUpdate = TimeSpan.Zero; private readonly SoundSpecifier _startSound = new SoundPathSpecifier(new ResPath("/Audio/Effects/eye_open.ogg")); private readonly SoundSpecifier _endSound = new SoundPathSpecifier(new ResPath("/Audio/Effects/eye_close.ogg")); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnHandleStateMarker); SubscribeLocalEvent>(OnPlayerAttached); SubscribeLocalEvent>(OnPlayerDetached); SubscribeLocalEvent(OnStatusEffectApplied); SubscribeLocalEvent(OnStatusEffectRemoved); } private void OnPlayerAttached(Entity ent, ref StatusEffectRelayedEvent args) { if (args.Args.Entity != _player.LocalEntity) return; ApplyOverlay(ent); } private void OnStatusEffectApplied(Entity ent, ref StatusEffectAppliedEvent args) { if (args.Target != _player.LocalEntity) return; //Prevents it from being applied twice if (_timing.IsFirstTimePredicted) return; ApplyOverlay(ent); } private void OnPlayerDetached(Entity ent, ref StatusEffectRelayedEvent args) { if (args.Args.Entity != _player.LocalEntity) return; RemoveOverlay(ent); } private void OnStatusEffectRemoved(Entity ent, ref StatusEffectRemovedEvent args) { if (args.Target != _player.LocalEntity) return; //Prevents it from beeing removed twice if (_timing.IsFirstTimePredicted) return; RemoveOverlay(ent); } protected override void OnExamined(Entity ent, ref ExaminedEvent args) { base.OnExamined(ent, ref args); if (ent.Comp.TargetCoordinates is null) return; var originPosition = _transform.GetWorldPosition(ent); var targetPosition = _transform.ToWorldPosition(ent.Comp.TargetCoordinates.Value); if ((targetPosition - originPosition).Length() < 0.5f) return; Angle angle = new(targetPosition - originPosition); var pointer = Spawn(ent.Comp.PointerProto, new MapCoordinates(originPosition, _transform.GetMapId(Transform(ent).Coordinates))); _transform.SetWorldRotation(pointer, angle + Angle.FromDegrees(90)); } private void OnHandleStateMarker(Entity ent, ref AfterAutoHandleStateEvent args) { if (!TryComp(ent, out var sprite)) return; if (ent.Comp.Icon is null) return; var layer = _sprite.AddLayer(ent.Owner, ent.Comp.Icon); sprite.LayerSetShader(layer, "unshaded"); _sprite.LayerSetScale(ent.Owner, layer, new Vector2(0.5f, 0.5f)); } public override void Update(float frameTime) { base.Update(frameTime); if (_timing.CurTime < _nextUpdate) return; _nextUpdate = _timing.CurTime + TimeSpan.FromSeconds(0.5f); var queryFade = EntityQueryEnumerator(); while (queryFade.MoveNext(out var uid, out var fade, out var sprite)) { UpdateOpaque((uid, fade), sprite); } } private void UpdateOpaque(Entity ent, SpriteComponent sprite) { var progress = Math.Clamp((_timing.CurTime.TotalSeconds - ent.Comp.SpawnTime.TotalSeconds) / (ent.Comp.EndTime.TotalSeconds - ent.Comp.SpawnTime.TotalSeconds), 0, 1); var alpha = 1 - progress; _sprite.SetColor((ent.Owner, sprite), Color.White.WithAlpha((float)alpha)); } private void ApplyOverlay(Entity ent) { _overlay = new CP14MagicVisionOverlay(); _overlayMan.AddOverlay(_overlay); _overlay.StartOverlay = _timing.CurTime; _overlay2 = new CP14MagicVisionNoirOverlay(); _overlayMan.AddOverlay(_overlay2); _audio.PlayGlobal(_startSound, ent); } private void RemoveOverlay(Entity ent) { // Check if it is the last Magic Vision Status Effect if (_status.HasEffectComp(_player.LocalEntity)) return; if (_overlay != null) { _overlayMan.RemoveOverlay(_overlay); _overlay = null; } if (_overlay2 != null) { _overlayMan.RemoveOverlay(_overlay2); _overlay2 = null; } _audio.PlayGlobal(_endSound, ent); } }