using Content.Server.Power.Components; using Content.Server.Medical.Components; using Content.Shared.DeviceLinking.Events; using Content.Shared._White.Medical.BodyScanner; using Robust.Server.GameObjects; using Robust.Shared.Timing; using Content.Shared.Mobs.Components; using Content.Server.Temperature.Components; using Content.Server.Body.Components; using Content.Server.Forensics; using Content.Server.Paper; using Content.Shared.Popups; using Content.Shared.Damage; using Content.Shared.Nutrition.Components; using Content.Shared.Damage.Components; using System.Text; using Content.Shared.FixedPoint; using Robust.Shared.Prototypes; using Content.Shared.Damage.Prototypes; using Content.Shared.Mind.Components; using Robust.Shared.Audio.Systems; namespace Content.Server._White.Medical.BodyScanner { public sealed class BodyScannerConsoleSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly PaperSystem _paper = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnNewLink); SubscribeLocalEvent(OnPortDisconnected); SubscribeLocalEvent(OnStartScanning); SubscribeLocalEvent(OnStartPrinting); } public override void Update(float frameTime) { base.Update(frameTime); var queryActiveBodyScanner = EntityQueryEnumerator(); while (queryActiveBodyScanner.MoveNext(out var uid, out var active, out var scan)) { UpdateUserInterface(scan.Owner, scan, active); if (_timing.CurTime - active.StartTime < scan.ScanDuration) continue; FinishScan(uid, scan, active); } var queryBodyInScanner = EntityQueryEnumerator(); while (queryBodyInScanner.MoveNext(out var uid, out var bodyInScanner, out var scan)) { if (bodyInScanner.MedicalCannerComponent?.BodyContainer.ContainedEntity == null) { RemComp(uid); UpdateUserInterface(scan.Owner, scan); } } } private void UpdateUserInterface( EntityUid uid, BodyScannerConsoleComponent? scanComponent = null, ActiveBodyScannerConsoleComponent? activeScanComponent = null) { if (!Resolve(uid, ref scanComponent)) return; var state = GetUserInterfaceState(scanComponent, activeScanComponent); scanComponent.LastScannedState = state; _uiSystem.SetUiState(uid, BodyScannerConsoleUIKey.Key, state); } private BodyScannerConsoleBoundUserInterfaceState GetUserInterfaceState( BodyScannerConsoleComponent consoleComponent, ActiveBodyScannerConsoleComponent? activeScanComponent = null) { BodyScannerConsoleBoundUserInterfaceState state = new(); if (consoleComponent?.GeneticScanner != null && TryComp(consoleComponent.GeneticScanner, out var scanner)) { state.ScannerConnected = consoleComponent.GeneticScanner != null; state.GeneticScannerInRange = consoleComponent.GeneticScannerInRange; state.CanScan = true; if (state.ScannerConnected && state.GeneticScannerInRange && activeScanComponent != null) { state.CanScan = false; state.CanPrint = false; state.Scanning = true; state.ScanTotalTime = consoleComponent.ScanDuration; state.ScanTimeRemaining = consoleComponent.ScanDuration - (_timing.CurTime - activeScanComponent.StartTime); return state; } var scanBody = scanner.BodyContainer.ContainedEntity; if (scanBody != null) { state.CanPrint = true; state.TargetEntityUid = GetNetEntity(scanBody); state.EntityName = MetaData(scanBody.Value).EntityName; if (TryComp(scanBody, out var bloodstream)) { state.BleedAmount = bloodstream.BleedAmount; state.BloodMaxVolume = bloodstream.BloodMaxVolume; state.BloodReagent = bloodstream.BloodReagent; state.BloodSolution.MaxVolume = bloodstream.BloodMaxVolume; state.ChemicalMaxVolume = bloodstream.ChemicalMaxVolume; state.ChemicalSolution.MaxVolume = bloodstream.ChemicalMaxVolume; if (bloodstream.BloodSolution != null) state.BloodSolution = bloodstream.BloodSolution.Value.Comp.Solution.Clone(); if (bloodstream.ChemicalSolution != null) state.ChemicalSolution = bloodstream.ChemicalSolution.Value.Comp.Solution.Clone(); } if (TryComp(scanBody, out var dna)) { state.DNA = dna.DNA; } if (TryComp(scanBody, out var fingerprint)) { state.Fingerprint = fingerprint.Fingerprint ?? ""; } if (TryComp(scanBody, out var mind)) { state.HasMind = mind.HasMind; } if (TryComp(scanBody, out var respirator)) { state.MaxSaturation = respirator.MaxSaturation; state.MinSaturation = respirator.MinSaturation; state.Saturation = respirator.Saturation; } if (TryComp(scanBody, out var temp)) { state.HeatDamageThreshold = temp.HeatDamageThreshold; state.ColdDamageThreshold = temp.ColdDamageThreshold; state.CurrentTemperature = temp.CurrentTemperature; } if (TryComp(scanBody, out var thirst)) { state.CurrentThirst = thirst.CurrentThirst; state.CurrentThirstThreshold = (byte) thirst.CurrentThirstThreshold; } if (TryComp(scanBody, out var damageable)) { state.TotalDamage = damageable.TotalDamage; state.DamageDict = new(damageable.Damage.DamageDict); state.DamagePerGroup = new(damageable.DamagePerGroup); } if (TryComp(scanBody, out var hunger)) { state.CurrentHunger = hunger.CurrentHunger; state.CurrentHungerThreshold = (byte) hunger.CurrentThreshold; } if (TryComp(scanBody, out var mobState)) { state.CurrentState = mobState.CurrentState; } if (TryComp(scanBody, out var mobThresholds)) { foreach (var pair in mobThresholds.Thresholds) { if (pair.Value == Shared.Mobs.MobState.Dead) { state.DeadThreshold = pair.Key; } } } if (TryComp(scanBody, out var stamina)) { state.StaminaCritThreshold = stamina.CritThreshold; state.StaminaDamage = stamina.StaminaDamage; } } } return state; } public void FinishScan(EntityUid uid, BodyScannerConsoleComponent? component = null, ActiveBodyScannerConsoleComponent? active = null) { if (!Resolve(uid, ref component, ref active)) return; _audio.PlayPvs(component.ScanFinishedSound, uid); RemComp(uid); if (component?.GeneticScanner != null && TryComp(component.GeneticScanner, out var scanner)) { EnsureComp(uid).MedicalCannerComponent = scanner; } UpdateUserInterface(uid, component); } public void OnMapInit(EntityUid uid, BodyScannerConsoleComponent component, MapInitEvent args) { if (!_uiSystem.HasUi(uid, BodyScannerConsoleUIKey.Key)) return; UpdateUserInterface(uid, component); } private void OnNewLink(EntityUid uid, BodyScannerConsoleComponent component, NewLinkEvent args) { if (TryComp(args.Sink, out var scanner) && args.SinkPort == BodyScannerConsoleComponent.ScannerPort) { component.GeneticScanner = args.Sink; scanner.ConnectedConsole = uid; } } private void OnPortDisconnected(EntityUid uid, BodyScannerConsoleComponent component, PortDisconnectedEvent args) { if (args.Port != BodyScannerConsoleComponent.ScannerPort) return; if (TryComp(component.GeneticScanner, out var scanner)) scanner.ConnectedConsole = null; component.GeneticScanner = null; FinishScan(uid, component); } private void OnStartScanning(EntityUid uid, BodyScannerConsoleComponent component, BodyScannerStartScanningMessage msg) { if (component.GeneticScanner == null) return; if (!TryComp(component.GeneticScanner, out var scanner)) return; if (scanner.BodyContainer.ContainedEntity == null) return; var activeComp = EnsureComp(uid); activeComp.StartTime = _timing.CurTime; } private void OnStartPrinting(EntityUid uid, BodyScannerConsoleComponent component, BodyScannerStartPrintingMessage args) { var state = component.LastScannedState; if (state == null) return; state.CanPrint = false; _uiSystem.SetUiState(uid, BodyScannerConsoleUIKey.Key, state); var report = Spawn(component.ReportEntityId, Transform(uid).Coordinates); _metaSystem.SetEntityName(report, Loc.GetString("body-scanner-console-report-title", ("name", state.EntityName))); StringBuilder text = new(); text.AppendLine(state.EntityName); text.AppendLine(); text.AppendLine(Loc.GetString("body-scanner-console-report-temperature", ("amount", $"{state.CurrentTemperature - 273f:F1}"))); text.AppendLine(Loc.GetString("body-scanner-console-report-blood-level", ("amount", $"{state.BloodSolution.FillFraction * 100:F1}"))); text.AppendLine(Loc.GetString("body-scanner-console-report-total-damage", ("amount", state.TotalDamage.ToString()))); text.AppendLine(); HashSet shownTypes = new(); var protos = IoCManager.Resolve(); IReadOnlyDictionary damagePerGroup = state.DamagePerGroup; IReadOnlyDictionary damagePerType = state.DamageDict; foreach (var (damageGroupId, damageAmount) in damagePerGroup) { text.AppendLine(Loc.GetString("health-analyzer-window-damage-group-" + damageGroupId, ("amount", damageAmount))); var group = protos.Index(damageGroupId); foreach (var type in group.DamageTypes) { if (damagePerType.TryGetValue(type, out var typeAmount)) { // If damage types are allowed to belong to more than one damage group, they may appear twice here. Mark them as duplicate. if (!shownTypes.Contains(type)) { shownTypes.Add(type); text.Append(" - "); text.AppendLine(Loc.GetString("health-analyzer-window-damage-type-" + type, ("amount", typeAmount))); } } } text.AppendLine(); } text.AppendLine(Loc.GetString("body-scanner-console-window-temperature-group-text")); text.AppendLine(Loc.GetString("body-scanner-console-window-temperature-current-temperature-text", ("amount", $"{state.CurrentTemperature - 273:f1}"))); text.AppendLine(Loc.GetString( "body-scanner-console-window-temperature-heat-damage-threshold-temperature-text", ("amount", $"{state.HeatDamageThreshold - 273:f1}"))); text.AppendLine(Loc.GetString( "body-scanner-console-window-temperature-cold-damage-threshold-temperature-text", ("amount", $"{state.ColdDamageThreshold - 273:f1}"))); text.AppendLine(); text.AppendLine(Loc.GetString("body-scanner-console-window-saturation-group-text")); text.AppendLine(Loc.GetString("body-scanner-console-window-saturation-current-saturation-text", ("amount", $"{state.Saturation:f1}"))); text.AppendLine(Loc.GetString("body-scanner-console-window-saturation-maximum-saturation-text", ("amount", $"{state.MinSaturation:f1}"))); text.AppendLine(Loc.GetString("body-scanner-console-window-saturation-minimum-saturation-text", ("amount", $"{state.MaxSaturation:f1}"))); text.AppendLine(); text.AppendLine(Loc.GetString("body-scanner-console-window-thirst-group-text")); text.AppendLine(Loc.GetString("body-scanner-console-window-thirst-current-thirst-text", ("amount", $"{state.CurrentThirst:f1}"))); text.AppendLine(Loc.GetString("body-scanner-console-window-thirst-current-thirst-status-text", ("status", Loc.GetString("body-scanner-console-window-thirst-current-thirst-status-" + state.CurrentThirstThreshold)))); text.AppendLine(); text.AppendLine(Loc.GetString("body-scanner-console-window-hunger-group-text")); text.AppendLine(Loc.GetString("body-scanner-console-window-hunger-current-hunger-text", ("amount", $"{state.CurrentHunger:f1}"))); text.AppendLine(Loc.GetString("body-scanner-console-window-hunger-current-hunger-status-text", ("status", Loc.GetString("body-scanner-console-window-hunger-current-hunger-status-" + state.CurrentHungerThreshold)))); text.AppendLine(); text.AppendLine(Loc.GetString("body-scanner-console-window-blood-solutions-group-text")); text.AppendLine(Loc.GetString("body-scanner-console-window-blood-solutions-volume-group-text", ("amount", $"{state.BloodSolution.Volume.Float():f1}"), ("maxAmount", $"{state.BloodSolution.MaxVolume.Float():f1}"), ("temperature", $"{state.BloodSolution.Temperature - 271:f1}"))); state.BloodSolution.Contents.ForEach(x => { text.Append(" - "); text.AppendLine($"{x.Reagent.Prototype}: {x.Quantity}"); }); text.AppendLine(); text.AppendLine(Loc.GetString("body-scanner-console-window-chemical-solutions-group-text")); text.AppendLine(Loc.GetString("body-scanner-console-window-chemical-solutions-volume-group-text", ("amount", $"{state.ChemicalSolution.Volume.Float():f1}"), ("maxAmount", $"{state.ChemicalSolution.MaxVolume.Float():f1}"), ("temperature", $"{state.ChemicalSolution.Temperature - 271:f1}"))); state.ChemicalSolution.Contents.ForEach(x => { text.Append(" - "); text.AppendLine($"{x.Reagent.Prototype}: {x.Quantity}"); }); text.AppendLine(); text.AppendLine(Loc.GetString("body-scanner-console-window-dna-text", ("value", state.DNA))); text.AppendLine(Loc.GetString("body-scanner-console-window-fingerprint-text", ("value", $"{state.Fingerprint}"))); text.AppendLine(Loc.GetString("body-scanner-console-window-mind-text", ("value", $"{state.HasMind}"))); _audio.PlayPvs(component.PrintSound, uid); _popup.PopupEntity(Loc.GetString("body-scanner-console-print-popup"), uid); _paper.SetContent(report, text.ToString()); } private void OnPowerChanged(EntityUid uid, ActiveBodyScannerConsoleComponent component, ref PowerChangedEvent args) { if (args.Powered) return; RemComp(uid); } } }