using System.Linq; using System.Numerics; using Content.Client.Stealth; using Content.Shared._DV.Overlays.Components; using Content.Shared.Body.Components; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reagent; using Content.Shared.Stealth.Components; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Shared.Enums; using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Client._Goobstation.Overlays; public sealed class SharkVisionOverlay : Overlay { [Dependency] private readonly IEntityManager _entity = default!; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPrototypeManager _proto = default!; private readonly TransformSystem _transform; private readonly StealthSystem _stealth; private readonly ContainerSystem _container; private readonly SharedSolutionContainerSystem _solution; private readonly SpriteSystem _sprite; public override bool RequestScreenTexture => true; public override OverlaySpace Space => OverlaySpace.WorldSpace; private readonly List _entries = []; private EntityUid? _lightEntity; public float LightRadius; public SharkVisionComponent? Comp; public SharkVisionOverlay() { IoCManager.InjectDependencies(this); _container = _entity.System(); _transform = _entity.System(); _stealth = _entity.System(); _solution = _entity.System(); _sprite = _entity.System(); ZIndex = -1; } protected override void Draw(in OverlayDrawArgs args) { if (ScreenTexture is null || Comp is null) return; var worldHandle = args.WorldHandle; var eye = args.Viewport.Eye; if (eye == null) return; var player = _player.LocalEntity; if (!_entity.TryGetComponent(player, out TransformComponent? playerXform)) return; var accumulator = Math.Clamp(Comp.PulseAccumulator, 0f, Comp.PulseTime); var alpha = Comp.PulseTime <= 0f ? 1f : float.Lerp(1f, 0f, accumulator / Comp.PulseTime); var mapId = eye.Position.MapId; var eyeRot = eye.Rotation; GetVisionEntities(Comp.BloodPrototypes, mapId, eyeRot); foreach (var entry in _entries) { Render(entry.Ent, entry.Map, worldHandle, entry.EyeRot, Comp.Color, alpha); } worldHandle.SetTransform(Matrix3x2.Identity); } private void GetVisionEntities(ProtoId[] bloodPrototypes, MapId mapId, Angle eyeRot) { _entries.Clear(); var entities = _entity.EntityQueryEnumerator(); while (entities.MoveNext(out var uid, out var body, out var sprite, out var xform)) { if (!CanSee(uid, sprite)) continue; // Luckily, players are also SolutionContainerManagerComponent, so let's check for a bloodstream and check for bleeds if (_entity.TryGetComponent(uid, out var bloodstream) && bloodstream.BleedAmount <= 0) continue; // Should always be true but I'm just simplifying a future if statement if (!_entity.TryGetComponent(uid, out var solutionContainer)) continue; if (solutionContainer.Containers == null) continue; var bloodFound = false; // Someone calculate the Big O notation of this lmao foreach (var individualContainer in solutionContainer.Containers) { if (_solution.TryGetSolution((uid, solutionContainer), individualContainer, out var _, out var solution)) { var reagentsInContainer = solution.GetReagentPrototypes(_proto).ToDictionary(); bloodFound = reagentsInContainer.Keys.Any(reagentKey => bloodPrototypes.Any(x => reagentKey == x)); } if (bloodFound) break; // We don't need to keep searching if the entity has blood in at least one container } if (!bloodFound) continue; var entity = uid; // Parent container check if (_container.TryGetOuterContainer(uid, xform, out var container)) { var owner = container.Owner; if (_entity.TryGetComponent(owner, out var ownerSprite) && _entity.TryGetComponent(owner, out var ownerXform)) { entity = owner; sprite = ownerSprite; xform = ownerXform; } } if (_entries.Any(e => e.Ent.Owner == entity)) continue; _entries.Add(new SharkVisionRenderEntry((entity, sprite, xform), mapId, eyeRot)); } } private void Render(Entity ent, MapId? map, DrawingHandleWorld handle, Angle eyeRot, Color color, float alpha) { var (uid, sprite, xform) = ent; if (xform.MapID != map || !CanSee(uid, sprite)) return; var position = _transform.GetWorldPosition(xform); var rotation = _transform.GetWorldRotation(xform); var originalColor = sprite.Color; _sprite.SetColor((ent, ent.Comp1), color.WithAlpha(alpha)); _sprite.RenderSprite((ent, ent.Comp1), handle, eyeRot, rotation, position); _sprite.SetColor((ent, ent.Comp1), originalColor); } private bool CanSee(EntityUid uid, SpriteComponent sprite) { return sprite.Visible && (!_entity.TryGetComponent(uid, out StealthComponent? stealth) || _stealth.GetVisibility(uid, stealth) > 0.5f); } public void ResetLight(bool checkFirstTimePredicted = true) { if (_lightEntity == null || checkFirstTimePredicted && !_timing.IsFirstTimePredicted) return; _entity.DeleteEntity(_lightEntity); _lightEntity = null; } } public record struct SharkVisionRenderEntry( Entity Ent, MapId? Map, Angle EyeRot);