| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- using System.Linq;
- using Content.Server.Power.Components;
- using Content.Server.Research.Systems;
- using Content.Shared.UserInterface;
- using Content.Server.Xenoarchaeology.Equipment.Components;
- using Content.Server.Xenoarchaeology.XenoArtifacts;
- using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
- using Content.Shared.Audio;
- using Content.Shared.DeviceLinking;
- using Content.Shared.DeviceLinking.Events;
- using Content.Shared.Paper;
- using Content.Shared.Placeable;
- using Content.Shared.Popups;
- using Content.Shared.Power;
- using Content.Shared.Power.EntitySystems;
- using Content.Shared.Research.Components;
- using Content.Shared.Xenoarchaeology.Equipment;
- using Content.Shared.Xenoarchaeology.XenoArtifacts;
- using JetBrains.Annotations;
- using Robust.Server.GameObjects;
- using Robust.Shared.Audio;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Timing;
- using Robust.Shared.Utility;
- namespace Content.Server.Xenoarchaeology.Equipment.Systems;
- /// <summary>
- /// This system is used for managing the artifact analyzer as well as the analysis console.
- /// It also hanadles scanning and ui updates for both systems.
- /// </summary>
- public sealed class ArtifactAnalyzerSystem : EntitySystem
- {
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IPrototypeManager _prototype = default!;
- [Dependency] private readonly ArtifactSystem _artifact = default!;
- [Dependency] private readonly MetaDataSystem _metaSystem = default!;
- [Dependency] private readonly PaperSystem _paper = default!;
- [Dependency] private readonly ResearchSystem _research = default!;
- [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!;
- [Dependency] private readonly TraversalDistorterSystem _traversalDistorter = default!;
- [Dependency] private readonly UserInterfaceSystem _ui = default!;
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<ActiveScannedArtifactComponent, ArtifactActivatedEvent>(OnArtifactActivated);
- SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentStartup>(OnAnalyzeStart);
- SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentShutdown>(OnAnalyzeEnd);
- SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, PowerChangedEvent>(OnPowerChanged);
- SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemPlacedEvent>(OnItemPlaced);
- SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemRemovedEvent>(OnItemRemoved);
- SubscribeLocalEvent<ArtifactAnalyzerComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<AnalysisConsoleComponent, NewLinkEvent>(OnNewLink);
- SubscribeLocalEvent<AnalysisConsoleComponent, PortDisconnectedEvent>(OnPortDisconnected);
- SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleServerSelectionMessage>(OnServerSelectionMessage);
- SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleScanButtonPressedMessage>(OnScanButton);
- SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsolePrintButtonPressedMessage>(OnPrintButton);
- SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleExtractButtonPressedMessage>(OnExtractButton);
- SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleBiasButtonPressedMessage>(OnBiasButton);
- SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerSelectedMessage>((e, c, _) => UpdateUserInterface(e, c),
- after: new[] { typeof(ResearchSystem) });
- SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerDeselectedMessage>((e, c, _) => UpdateUserInterface(e, c),
- after: new[] { typeof(ResearchSystem) });
- SubscribeLocalEvent<AnalysisConsoleComponent, BeforeActivatableUIOpenEvent>((e, c, _) => UpdateUserInterface(e, c));
- }
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- var query = EntityQueryEnumerator<ActiveArtifactAnalyzerComponent, ArtifactAnalyzerComponent>();
- while (query.MoveNext(out var uid, out var active, out var scan))
- {
- if (active.AnalysisPaused)
- continue;
- if (_timing.CurTime - active.StartTime < scan.AnalysisDuration - active.AccumulatedRunTime)
- continue;
- FinishScan(uid, scan, active);
- }
- }
- /// <summary>
- /// Resets the current scan on the artifact analyzer
- /// </summary>
- /// <param name="uid">The analyzer being reset</param>
- /// <param name="component"></param>
- [PublicAPI]
- public void ResetAnalyzer(EntityUid uid, ArtifactAnalyzerComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
- component.LastAnalyzedArtifact = null;
- component.ReadyToPrint = false;
- UpdateAnalyzerInformation(uid, component);
- }
- /// <summary>
- /// Goes through the current entities on
- /// the analyzer and returns a valid artifact
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="placer"></param>
- /// <returns></returns>
- private EntityUid? GetArtifactForAnalysis(EntityUid? uid, ItemPlacerComponent? placer = null)
- {
- if (uid == null || !Resolve(uid.Value, ref placer))
- return null;
- return placer.PlacedEntities.FirstOrNull();
- }
- /// <summary>
- /// Updates the current scan information based on
- /// the last artifact that was scanned.
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="component"></param>
- private void UpdateAnalyzerInformation(EntityUid uid, ArtifactAnalyzerComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
- if (component.LastAnalyzedArtifact == null)
- {
- component.LastAnalyzerPointValue = null;
- component.LastAnalyzedNode = null;
- }
- else if (TryComp<ArtifactComponent>(component.LastAnalyzedArtifact, out var artifact))
- {
- var lastNode = artifact.CurrentNodeId == null
- ? null
- : (ArtifactNode?) _artifact.GetNodeFromId(artifact.CurrentNodeId.Value, artifact).Clone();
- component.LastAnalyzedNode = lastNode;
- component.LastAnalyzerPointValue = _artifact.GetResearchPointValue(component.LastAnalyzedArtifact.Value, artifact);
- }
- }
- private void OnMapInit(EntityUid uid, ArtifactAnalyzerComponent component, MapInitEvent args)
- {
- if (!TryComp<DeviceLinkSinkComponent>(uid, out var sink))
- return;
- foreach (var source in sink.LinkedSources)
- {
- if (!TryComp<AnalysisConsoleComponent>(source, out var analysis))
- continue;
- component.Console = source;
- analysis.AnalyzerEntity = uid;
- return;
- }
- }
- private void OnNewLink(EntityUid uid, AnalysisConsoleComponent component, NewLinkEvent args)
- {
- if (!TryComp<ArtifactAnalyzerComponent>(args.Sink, out var analyzer))
- return;
- component.AnalyzerEntity = args.Sink;
- analyzer.Console = uid;
- UpdateUserInterface(uid, component);
- }
- private void OnPortDisconnected(EntityUid uid, AnalysisConsoleComponent component, PortDisconnectedEvent args)
- {
- if (args.Port == component.LinkingPort && component.AnalyzerEntity != null)
- {
- if (TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzezr))
- analyzezr.Console = null;
- component.AnalyzerEntity = null;
- }
- UpdateUserInterface(uid, component);
- }
- private void UpdateUserInterface(EntityUid uid, AnalysisConsoleComponent? component = null)
- {
- if (!Resolve(uid, ref component, false))
- return;
- EntityUid? artifact = null;
- FormattedMessage? msg = null;
- TimeSpan? totalTime = null;
- var canScan = false;
- var canPrint = false;
- var points = 0;
- if (TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzer))
- {
- artifact = analyzer.LastAnalyzedArtifact;
- msg = GetArtifactScanMessage(analyzer);
- totalTime = analyzer.AnalysisDuration;
- if (TryComp<ItemPlacerComponent>(component.AnalyzerEntity, out var placer))
- canScan = placer.PlacedEntities.Any();
- canPrint = analyzer.ReadyToPrint;
- // the artifact that's actually on the scanner right now.
- if (GetArtifactForAnalysis(component.AnalyzerEntity, placer) is { } current)
- points = _artifact.GetResearchPointValue(current);
- }
- var analyzerConnected = component.AnalyzerEntity != null;
- var serverConnected = TryComp<ResearchClientComponent>(uid, out var client) && client.ConnectedToServer;
- var scanning = TryComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity, out var active);
- var paused = active != null ? active.AnalysisPaused : false;
- var biasDirection = BiasDirection.Up;
- if (TryComp<TraversalDistorterComponent>(component.AnalyzerEntity, out var trav))
- biasDirection = trav.BiasDirection;
- var state = new AnalysisConsoleUpdateState(GetNetEntity(artifact), analyzerConnected, serverConnected,
- canScan, canPrint, msg, scanning, paused, active?.StartTime, active?.AccumulatedRunTime, totalTime, points, biasDirection == BiasDirection.Down);
- _ui.SetUiState(uid, ArtifactAnalzyerUiKey.Key, state);
- }
- /// <summary>
- /// opens the server selection menu.
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="component"></param>
- /// <param name="args"></param>
- private void OnServerSelectionMessage(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleServerSelectionMessage args)
- {
- _ui.OpenUi(uid, ResearchClientUiKey.Key, args.Actor);
- }
- /// <summary>
- /// Starts scanning the artifact.
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="component"></param>
- /// <param name="args"></param>
- private void OnScanButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleScanButtonPressedMessage args)
- {
- if (component.AnalyzerEntity == null)
- return;
- if (HasComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity))
- return;
- var ent = GetArtifactForAnalysis(component.AnalyzerEntity);
- if (ent == null)
- return;
- var activeComp = EnsureComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity.Value);
- activeComp.StartTime = _timing.CurTime;
- activeComp.AccumulatedRunTime = TimeSpan.Zero;
- activeComp.Artifact = ent.Value;
- if (TryComp<ApcPowerReceiverComponent>(component.AnalyzerEntity.Value, out var powa))
- activeComp.AnalysisPaused = !powa.Powered;
- var activeArtifact = EnsureComp<ActiveScannedArtifactComponent>(ent.Value);
- activeArtifact.Scanner = component.AnalyzerEntity.Value;
- UpdateUserInterface(uid, component);
- }
- private void OnPrintButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsolePrintButtonPressedMessage args)
- {
- if (component.AnalyzerEntity == null)
- return;
- if (!TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzer) ||
- analyzer.LastAnalyzedNode == null ||
- analyzer.LastAnalyzerPointValue == null ||
- !analyzer.ReadyToPrint)
- {
- return;
- }
- analyzer.ReadyToPrint = false;
- var report = Spawn(component.ReportEntityId, Transform(uid).Coordinates);
- _metaSystem.SetEntityName(report, Loc.GetString("analysis-report-title", ("id", analyzer.LastAnalyzedNode.Id)));
- var msg = GetArtifactScanMessage(analyzer);
- if (msg == null)
- return;
- _popup.PopupEntity(Loc.GetString("analysis-console-print-popup"), uid);
- if (TryComp<PaperComponent>(report, out var paperComp))
- _paper.SetContent((report, paperComp), msg.ToMarkup());
- UpdateUserInterface(uid, component);
- }
- private FormattedMessage? GetArtifactScanMessage(ArtifactAnalyzerComponent component)
- {
- var msg = new FormattedMessage();
- if (component.LastAnalyzedNode == null)
- return null;
- var n = component.LastAnalyzedNode;
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-id", ("id", n.Id)));
- msg.PushNewline();
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-depth", ("depth", n.Depth)));
- msg.PushNewline();
- var activated = n.Triggered
- ? "analysis-console-info-triggered-true"
- : "analysis-console-info-triggered-false";
- msg.AddMarkupOrThrow(Loc.GetString(activated));
- msg.PushNewline();
- msg.PushNewline();
- var needSecondNewline = false;
- var triggerProto = _prototype.Index<ArtifactTriggerPrototype>(n.Trigger);
- if (triggerProto.TriggerHint != null)
- {
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-trigger",
- ("trigger", Loc.GetString(triggerProto.TriggerHint))) + "\n");
- needSecondNewline = true;
- }
- var effectproto = _prototype.Index<ArtifactEffectPrototype>(n.Effect);
- if (effectproto.EffectHint != null)
- {
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-effect",
- ("effect", Loc.GetString(effectproto.EffectHint))) + "\n");
- needSecondNewline = true;
- }
- if (needSecondNewline)
- msg.PushNewline();
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-edges", ("edges", n.Edges.Count)));
- msg.PushNewline();
- if (component.LastAnalyzerPointValue != null)
- msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-value", ("value", component.LastAnalyzerPointValue)));
- return msg;
- }
- /// <summary>
- /// Extracts points from the artifact and updates the server points
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="component"></param>
- /// <param name="args"></param>
- private void OnExtractButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleExtractButtonPressedMessage args)
- {
- if (component.AnalyzerEntity == null)
- return;
- if (!_research.TryGetClientServer(uid, out var server, out var serverComponent))
- return;
- var artifact = GetArtifactForAnalysis(component.AnalyzerEntity);
- if (artifact == null)
- return;
- var pointValue = _artifact.GetResearchPointValue(artifact.Value);
- // no new nodes triggered so nothing to add
- if (pointValue == 0)
- return;
- _research.ModifyServerPoints(server.Value, pointValue, serverComponent);
- _artifact.AdjustConsumedPoints(artifact.Value, pointValue);
- _audio.PlayPvs(component.ExtractSound, component.AnalyzerEntity.Value, AudioParams.Default.WithVolume(2f));
- _popup.PopupEntity(Loc.GetString("analyzer-artifact-extract-popup"),
- component.AnalyzerEntity.Value, PopupType.Large);
- UpdateUserInterface(uid, component);
- }
- private void OnBiasButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleBiasButtonPressedMessage args)
- {
- if (component.AnalyzerEntity == null)
- return;
- if (!TryComp<TraversalDistorterComponent>(component.AnalyzerEntity, out var trav))
- return;
- if (!_traversalDistorter.SetState(component.AnalyzerEntity.Value, trav, args.IsDown))
- return;
- UpdateUserInterface(uid, component);
- }
- /// <summary>
- /// Cancels scans if the artifact changes nodes (is activated) during the scan.
- /// </summary>
- private void OnArtifactActivated(EntityUid uid, ActiveScannedArtifactComponent component, ArtifactActivatedEvent args)
- {
- CancelScan(uid);
- }
- /// <summary>
- /// Stops the current scan
- /// </summary>
- [PublicAPI]
- public void CancelScan(EntityUid artifact, ActiveScannedArtifactComponent? component = null, ArtifactAnalyzerComponent? analyzer = null)
- {
- if (!Resolve(artifact, ref component, false))
- return;
- if (!Resolve(component.Scanner, ref analyzer))
- return;
- _audio.PlayPvs(component.ScanFailureSound, component.Scanner, AudioParams.Default.WithVolume(3f));
- RemComp<ActiveArtifactAnalyzerComponent>(component.Scanner);
- if (analyzer.Console != null)
- UpdateUserInterface(analyzer.Console.Value);
- RemCompDeferred(artifact, component);
- }
- /// <summary>
- /// Finishes the current scan.
- /// </summary>
- [PublicAPI]
- public void FinishScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
- {
- if (!Resolve(uid, ref component, ref active))
- return;
- component.ReadyToPrint = true;
- _audio.PlayPvs(component.ScanFinishedSound, uid);
- component.LastAnalyzedArtifact = active.Artifact;
- UpdateAnalyzerInformation(uid, component);
- RemComp<ActiveScannedArtifactComponent>(active.Artifact);
- RemComp(uid, active);
- if (component.Console != null)
- UpdateUserInterface(component.Console.Value);
- }
- [PublicAPI]
- public void PauseScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
- {
- if (!Resolve(uid, ref component, ref active) || active.AnalysisPaused)
- return;
- active.AnalysisPaused = true;
- // As we pause, we store what was already completed.
- active.AccumulatedRunTime = (_timing.CurTime - active.StartTime) + active.AccumulatedRunTime;
- if (Exists(component.Console))
- UpdateUserInterface(component.Console.Value);
- }
- [PublicAPI]
- public void ResumeScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
- {
- if (!Resolve(uid, ref component, ref active) || !active.AnalysisPaused)
- return;
- active.StartTime = _timing.CurTime;
- active.AnalysisPaused = false;
- if (Exists(component.Console))
- UpdateUserInterface(component.Console.Value);
- }
- private void OnItemPlaced(EntityUid uid, ArtifactAnalyzerComponent component, ref ItemPlacedEvent args)
- {
- if (component.Console != null && Exists(component.Console))
- UpdateUserInterface(component.Console.Value);
- }
- private void OnItemRemoved(EntityUid uid, ArtifactAnalyzerComponent component, ref ItemRemovedEvent args)
- {
- // Scanners shouldn't give permanent remove vision to an artifact, and the scanned artifact doesn't have any
- // component to track analyzers that have scanned it for removal if the artifact gets deleted.
- // So we always clear this on removal.
- component.LastAnalyzedArtifact = null;
- // cancel the scan if the artifact moves off the analyzer
- CancelScan(args.OtherEntity);
- if (Exists(component.Console))
- UpdateUserInterface(component.Console.Value);
- }
- private void OnAnalyzeStart(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentStartup args)
- {
- _receiver.SetNeedsPower(uid, true);
- _ambientSound.SetAmbience(uid, true);
- }
- private void OnAnalyzeEnd(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentShutdown args)
- {
- _receiver.SetNeedsPower(uid, false);
- _ambientSound.SetAmbience(uid, false);
- }
- private void OnPowerChanged(EntityUid uid, ActiveArtifactAnalyzerComponent active, ref PowerChangedEvent args)
- {
- if (!args.Powered)
- {
- PauseScan(uid, null, active);
- }
- else
- {
- ResumeScan(uid, null, active);
- }
- }
- }
|