using Content.Server.Body.Components; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Medical.Components; using Content.Server.PowerCell; using Content.Server.Temperature.Components; using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Item.ItemToggle; using Content.Shared.Item.ItemToggle.Components; using Content.Shared.MedicalScanner; using Content.Shared.Mobs.Components; using Content.Shared.Popups; using Content.Shared.PowerCell; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Player; using Robust.Shared.Timing; namespace Content.Server.Medical; public sealed class HealthAnalyzerSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly PowerCellSystem _cell = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly ItemToggleSystem _toggle = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; public override void Initialize() { SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnInsertedIntoContainer); SubscribeLocalEvent(OnToggled); SubscribeLocalEvent(OnDropped); } public override void Update(float frameTime) { var analyzerQuery = EntityQueryEnumerator(); while (analyzerQuery.MoveNext(out var uid, out var component, out var transform)) { //Update rate limited to 1 second if (component.NextUpdate > _timing.CurTime) continue; if (component.ScannedEntity is not {} patient) continue; if (Deleted(patient)) { StopAnalyzingEntity((uid, component), patient); continue; } component.NextUpdate = _timing.CurTime + component.UpdateInterval; //Get distance between health analyzer and the scanned entity var patientCoordinates = Transform(patient).Coordinates; if (!_transformSystem.InRange(patientCoordinates, transform.Coordinates, component.MaxScanRange)) { //Range too far, disable updates StopAnalyzingEntity((uid, component), patient); continue; } UpdateScannedUser(uid, patient, true); } } /// /// Trigger the doafter for scanning /// private void OnAfterInteract(Entity uid, ref AfterInteractEvent args) { if (args.Target == null || !args.CanReach || !HasComp(args.Target) || !_cell.HasDrawCharge(uid, user: args.User)) return; _audio.PlayPvs(uid.Comp.ScanningBeginSound, uid); _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, uid.Comp.ScanDelay, new HealthAnalyzerDoAfterEvent(), uid, target: args.Target, used: uid) { NeedHand = true, BreakOnMove = true }); var msg = Loc.GetString("health-analyzer-popup-scan-target", ("user", Identity.Entity(args.User, EntityManager))); _popupSystem.PopupEntity(msg, args.Target.Value, args.Target.Value, PopupType.Medium); } private void OnDoAfter(Entity uid, ref HealthAnalyzerDoAfterEvent args) { if (args.Handled || args.Cancelled || args.Target == null || !_cell.HasDrawCharge(uid, user: args.User)) return; _audio.PlayPvs(uid.Comp.ScanningEndSound, uid); OpenUserInterface(args.User, uid); BeginAnalyzingEntity(uid, args.Target.Value); args.Handled = true; } /// /// Turn off when placed into a storage item or moved between slots/hands /// private void OnInsertedIntoContainer(Entity uid, ref EntGotInsertedIntoContainerMessage args) { if (uid.Comp.ScannedEntity is { } patient) _toggle.TryDeactivate(uid.Owner); } /// /// Disable continuous updates once turned off /// private void OnToggled(Entity ent, ref ItemToggledEvent args) { if (!args.Activated && ent.Comp.ScannedEntity is { } patient) StopAnalyzingEntity(ent, patient); } /// /// Turn off the analyser when dropped /// private void OnDropped(Entity uid, ref DroppedEvent args) { if (uid.Comp.ScannedEntity is { } patient) _toggle.TryDeactivate(uid.Owner); } private void OpenUserInterface(EntityUid user, EntityUid analyzer) { if (!_uiSystem.HasUi(analyzer, HealthAnalyzerUiKey.Key)) return; _uiSystem.OpenUi(analyzer, HealthAnalyzerUiKey.Key, user); } /// /// Mark the entity as having its health analyzed, and link the analyzer to it /// /// The health analyzer that should receive the updates /// The entity to start analyzing private void BeginAnalyzingEntity(Entity healthAnalyzer, EntityUid target) { //Link the health analyzer to the scanned entity healthAnalyzer.Comp.ScannedEntity = target; _toggle.TryActivate(healthAnalyzer.Owner); UpdateScannedUser(healthAnalyzer, target, true); } /// /// Remove the analyzer from the active list, and remove the component if it has no active analyzers /// /// The health analyzer that's receiving the updates /// The entity to analyze private void StopAnalyzingEntity(Entity healthAnalyzer, EntityUid target) { //Unlink the analyzer healthAnalyzer.Comp.ScannedEntity = null; _toggle.TryDeactivate(healthAnalyzer.Owner); UpdateScannedUser(healthAnalyzer, target, false); } /// /// Send an update for the target to the healthAnalyzer /// /// The health analyzer /// The entity being scanned /// True makes the UI show ACTIVE, False makes the UI show INACTIVE public void UpdateScannedUser(EntityUid healthAnalyzer, EntityUid target, bool scanMode) { if (!_uiSystem.HasUi(healthAnalyzer, HealthAnalyzerUiKey.Key)) return; if (!HasComp(target)) return; var bodyTemperature = float.NaN; if (TryComp(target, out var temp)) bodyTemperature = temp.CurrentTemperature; var bloodAmount = float.NaN; var bleeding = false; if (TryComp(target, out var bloodstream) && _solutionContainerSystem.ResolveSolution(target, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution)) { bloodAmount = bloodSolution.FillFraction; bleeding = bloodstream.BleedAmount > 0; } _uiSystem.ServerSendUiMessage(healthAnalyzer, HealthAnalyzerUiKey.Key, new HealthAnalyzerScannedUserMessage( GetNetEntity(target), bodyTemperature, bloodAmount, scanMode, bleeding )); } }