ArtifactAnalyzerSystem.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. using System.Linq;
  2. using Content.Server.Power.Components;
  3. using Content.Server.Research.Systems;
  4. using Content.Shared.UserInterface;
  5. using Content.Server.Xenoarchaeology.Equipment.Components;
  6. using Content.Server.Xenoarchaeology.XenoArtifacts;
  7. using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
  8. using Content.Shared.Audio;
  9. using Content.Shared.DeviceLinking;
  10. using Content.Shared.DeviceLinking.Events;
  11. using Content.Shared.Paper;
  12. using Content.Shared.Placeable;
  13. using Content.Shared.Popups;
  14. using Content.Shared.Power;
  15. using Content.Shared.Power.EntitySystems;
  16. using Content.Shared.Research.Components;
  17. using Content.Shared.Xenoarchaeology.Equipment;
  18. using Content.Shared.Xenoarchaeology.XenoArtifacts;
  19. using JetBrains.Annotations;
  20. using Robust.Server.GameObjects;
  21. using Robust.Shared.Audio;
  22. using Robust.Shared.Audio.Systems;
  23. using Robust.Shared.Prototypes;
  24. using Robust.Shared.Timing;
  25. using Robust.Shared.Utility;
  26. namespace Content.Server.Xenoarchaeology.Equipment.Systems;
  27. /// <summary>
  28. /// This system is used for managing the artifact analyzer as well as the analysis console.
  29. /// It also hanadles scanning and ui updates for both systems.
  30. /// </summary>
  31. public sealed class ArtifactAnalyzerSystem : EntitySystem
  32. {
  33. [Dependency] private readonly IGameTiming _timing = default!;
  34. [Dependency] private readonly IPrototypeManager _prototype = default!;
  35. [Dependency] private readonly ArtifactSystem _artifact = default!;
  36. [Dependency] private readonly MetaDataSystem _metaSystem = default!;
  37. [Dependency] private readonly PaperSystem _paper = default!;
  38. [Dependency] private readonly ResearchSystem _research = default!;
  39. [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
  40. [Dependency] private readonly SharedAudioSystem _audio = default!;
  41. [Dependency] private readonly SharedPopupSystem _popup = default!;
  42. [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!;
  43. [Dependency] private readonly TraversalDistorterSystem _traversalDistorter = default!;
  44. [Dependency] private readonly UserInterfaceSystem _ui = default!;
  45. /// <inheritdoc/>
  46. public override void Initialize()
  47. {
  48. SubscribeLocalEvent<ActiveScannedArtifactComponent, ArtifactActivatedEvent>(OnArtifactActivated);
  49. SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentStartup>(OnAnalyzeStart);
  50. SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentShutdown>(OnAnalyzeEnd);
  51. SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, PowerChangedEvent>(OnPowerChanged);
  52. SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemPlacedEvent>(OnItemPlaced);
  53. SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemRemovedEvent>(OnItemRemoved);
  54. SubscribeLocalEvent<ArtifactAnalyzerComponent, MapInitEvent>(OnMapInit);
  55. SubscribeLocalEvent<AnalysisConsoleComponent, NewLinkEvent>(OnNewLink);
  56. SubscribeLocalEvent<AnalysisConsoleComponent, PortDisconnectedEvent>(OnPortDisconnected);
  57. SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleServerSelectionMessage>(OnServerSelectionMessage);
  58. SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleScanButtonPressedMessage>(OnScanButton);
  59. SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsolePrintButtonPressedMessage>(OnPrintButton);
  60. SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleExtractButtonPressedMessage>(OnExtractButton);
  61. SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleBiasButtonPressedMessage>(OnBiasButton);
  62. SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerSelectedMessage>((e, c, _) => UpdateUserInterface(e, c),
  63. after: new[] { typeof(ResearchSystem) });
  64. SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerDeselectedMessage>((e, c, _) => UpdateUserInterface(e, c),
  65. after: new[] { typeof(ResearchSystem) });
  66. SubscribeLocalEvent<AnalysisConsoleComponent, BeforeActivatableUIOpenEvent>((e, c, _) => UpdateUserInterface(e, c));
  67. }
  68. public override void Update(float frameTime)
  69. {
  70. base.Update(frameTime);
  71. var query = EntityQueryEnumerator<ActiveArtifactAnalyzerComponent, ArtifactAnalyzerComponent>();
  72. while (query.MoveNext(out var uid, out var active, out var scan))
  73. {
  74. if (active.AnalysisPaused)
  75. continue;
  76. if (_timing.CurTime - active.StartTime < scan.AnalysisDuration - active.AccumulatedRunTime)
  77. continue;
  78. FinishScan(uid, scan, active);
  79. }
  80. }
  81. /// <summary>
  82. /// Resets the current scan on the artifact analyzer
  83. /// </summary>
  84. /// <param name="uid">The analyzer being reset</param>
  85. /// <param name="component"></param>
  86. [PublicAPI]
  87. public void ResetAnalyzer(EntityUid uid, ArtifactAnalyzerComponent? component = null)
  88. {
  89. if (!Resolve(uid, ref component))
  90. return;
  91. component.LastAnalyzedArtifact = null;
  92. component.ReadyToPrint = false;
  93. UpdateAnalyzerInformation(uid, component);
  94. }
  95. /// <summary>
  96. /// Goes through the current entities on
  97. /// the analyzer and returns a valid artifact
  98. /// </summary>
  99. /// <param name="uid"></param>
  100. /// <param name="placer"></param>
  101. /// <returns></returns>
  102. private EntityUid? GetArtifactForAnalysis(EntityUid? uid, ItemPlacerComponent? placer = null)
  103. {
  104. if (uid == null || !Resolve(uid.Value, ref placer))
  105. return null;
  106. return placer.PlacedEntities.FirstOrNull();
  107. }
  108. /// <summary>
  109. /// Updates the current scan information based on
  110. /// the last artifact that was scanned.
  111. /// </summary>
  112. /// <param name="uid"></param>
  113. /// <param name="component"></param>
  114. private void UpdateAnalyzerInformation(EntityUid uid, ArtifactAnalyzerComponent? component = null)
  115. {
  116. if (!Resolve(uid, ref component))
  117. return;
  118. if (component.LastAnalyzedArtifact == null)
  119. {
  120. component.LastAnalyzerPointValue = null;
  121. component.LastAnalyzedNode = null;
  122. }
  123. else if (TryComp<ArtifactComponent>(component.LastAnalyzedArtifact, out var artifact))
  124. {
  125. var lastNode = artifact.CurrentNodeId == null
  126. ? null
  127. : (ArtifactNode?) _artifact.GetNodeFromId(artifact.CurrentNodeId.Value, artifact).Clone();
  128. component.LastAnalyzedNode = lastNode;
  129. component.LastAnalyzerPointValue = _artifact.GetResearchPointValue(component.LastAnalyzedArtifact.Value, artifact);
  130. }
  131. }
  132. private void OnMapInit(EntityUid uid, ArtifactAnalyzerComponent component, MapInitEvent args)
  133. {
  134. if (!TryComp<DeviceLinkSinkComponent>(uid, out var sink))
  135. return;
  136. foreach (var source in sink.LinkedSources)
  137. {
  138. if (!TryComp<AnalysisConsoleComponent>(source, out var analysis))
  139. continue;
  140. component.Console = source;
  141. analysis.AnalyzerEntity = uid;
  142. return;
  143. }
  144. }
  145. private void OnNewLink(EntityUid uid, AnalysisConsoleComponent component, NewLinkEvent args)
  146. {
  147. if (!TryComp<ArtifactAnalyzerComponent>(args.Sink, out var analyzer))
  148. return;
  149. component.AnalyzerEntity = args.Sink;
  150. analyzer.Console = uid;
  151. UpdateUserInterface(uid, component);
  152. }
  153. private void OnPortDisconnected(EntityUid uid, AnalysisConsoleComponent component, PortDisconnectedEvent args)
  154. {
  155. if (args.Port == component.LinkingPort && component.AnalyzerEntity != null)
  156. {
  157. if (TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzezr))
  158. analyzezr.Console = null;
  159. component.AnalyzerEntity = null;
  160. }
  161. UpdateUserInterface(uid, component);
  162. }
  163. private void UpdateUserInterface(EntityUid uid, AnalysisConsoleComponent? component = null)
  164. {
  165. if (!Resolve(uid, ref component, false))
  166. return;
  167. EntityUid? artifact = null;
  168. FormattedMessage? msg = null;
  169. TimeSpan? totalTime = null;
  170. var canScan = false;
  171. var canPrint = false;
  172. var points = 0;
  173. if (TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzer))
  174. {
  175. artifact = analyzer.LastAnalyzedArtifact;
  176. msg = GetArtifactScanMessage(analyzer);
  177. totalTime = analyzer.AnalysisDuration;
  178. if (TryComp<ItemPlacerComponent>(component.AnalyzerEntity, out var placer))
  179. canScan = placer.PlacedEntities.Any();
  180. canPrint = analyzer.ReadyToPrint;
  181. // the artifact that's actually on the scanner right now.
  182. if (GetArtifactForAnalysis(component.AnalyzerEntity, placer) is { } current)
  183. points = _artifact.GetResearchPointValue(current);
  184. }
  185. var analyzerConnected = component.AnalyzerEntity != null;
  186. var serverConnected = TryComp<ResearchClientComponent>(uid, out var client) && client.ConnectedToServer;
  187. var scanning = TryComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity, out var active);
  188. var paused = active != null ? active.AnalysisPaused : false;
  189. var biasDirection = BiasDirection.Up;
  190. if (TryComp<TraversalDistorterComponent>(component.AnalyzerEntity, out var trav))
  191. biasDirection = trav.BiasDirection;
  192. var state = new AnalysisConsoleUpdateState(GetNetEntity(artifact), analyzerConnected, serverConnected,
  193. canScan, canPrint, msg, scanning, paused, active?.StartTime, active?.AccumulatedRunTime, totalTime, points, biasDirection == BiasDirection.Down);
  194. _ui.SetUiState(uid, ArtifactAnalzyerUiKey.Key, state);
  195. }
  196. /// <summary>
  197. /// opens the server selection menu.
  198. /// </summary>
  199. /// <param name="uid"></param>
  200. /// <param name="component"></param>
  201. /// <param name="args"></param>
  202. private void OnServerSelectionMessage(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleServerSelectionMessage args)
  203. {
  204. _ui.OpenUi(uid, ResearchClientUiKey.Key, args.Actor);
  205. }
  206. /// <summary>
  207. /// Starts scanning the artifact.
  208. /// </summary>
  209. /// <param name="uid"></param>
  210. /// <param name="component"></param>
  211. /// <param name="args"></param>
  212. private void OnScanButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleScanButtonPressedMessage args)
  213. {
  214. if (component.AnalyzerEntity == null)
  215. return;
  216. if (HasComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity))
  217. return;
  218. var ent = GetArtifactForAnalysis(component.AnalyzerEntity);
  219. if (ent == null)
  220. return;
  221. var activeComp = EnsureComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity.Value);
  222. activeComp.StartTime = _timing.CurTime;
  223. activeComp.AccumulatedRunTime = TimeSpan.Zero;
  224. activeComp.Artifact = ent.Value;
  225. if (TryComp<ApcPowerReceiverComponent>(component.AnalyzerEntity.Value, out var powa))
  226. activeComp.AnalysisPaused = !powa.Powered;
  227. var activeArtifact = EnsureComp<ActiveScannedArtifactComponent>(ent.Value);
  228. activeArtifact.Scanner = component.AnalyzerEntity.Value;
  229. UpdateUserInterface(uid, component);
  230. }
  231. private void OnPrintButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsolePrintButtonPressedMessage args)
  232. {
  233. if (component.AnalyzerEntity == null)
  234. return;
  235. if (!TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzer) ||
  236. analyzer.LastAnalyzedNode == null ||
  237. analyzer.LastAnalyzerPointValue == null ||
  238. !analyzer.ReadyToPrint)
  239. {
  240. return;
  241. }
  242. analyzer.ReadyToPrint = false;
  243. var report = Spawn(component.ReportEntityId, Transform(uid).Coordinates);
  244. _metaSystem.SetEntityName(report, Loc.GetString("analysis-report-title", ("id", analyzer.LastAnalyzedNode.Id)));
  245. var msg = GetArtifactScanMessage(analyzer);
  246. if (msg == null)
  247. return;
  248. _popup.PopupEntity(Loc.GetString("analysis-console-print-popup"), uid);
  249. if (TryComp<PaperComponent>(report, out var paperComp))
  250. _paper.SetContent((report, paperComp), msg.ToMarkup());
  251. UpdateUserInterface(uid, component);
  252. }
  253. private FormattedMessage? GetArtifactScanMessage(ArtifactAnalyzerComponent component)
  254. {
  255. var msg = new FormattedMessage();
  256. if (component.LastAnalyzedNode == null)
  257. return null;
  258. var n = component.LastAnalyzedNode;
  259. msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-id", ("id", n.Id)));
  260. msg.PushNewline();
  261. msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-depth", ("depth", n.Depth)));
  262. msg.PushNewline();
  263. var activated = n.Triggered
  264. ? "analysis-console-info-triggered-true"
  265. : "analysis-console-info-triggered-false";
  266. msg.AddMarkupOrThrow(Loc.GetString(activated));
  267. msg.PushNewline();
  268. msg.PushNewline();
  269. var needSecondNewline = false;
  270. var triggerProto = _prototype.Index<ArtifactTriggerPrototype>(n.Trigger);
  271. if (triggerProto.TriggerHint != null)
  272. {
  273. msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-trigger",
  274. ("trigger", Loc.GetString(triggerProto.TriggerHint))) + "\n");
  275. needSecondNewline = true;
  276. }
  277. var effectproto = _prototype.Index<ArtifactEffectPrototype>(n.Effect);
  278. if (effectproto.EffectHint != null)
  279. {
  280. msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-effect",
  281. ("effect", Loc.GetString(effectproto.EffectHint))) + "\n");
  282. needSecondNewline = true;
  283. }
  284. if (needSecondNewline)
  285. msg.PushNewline();
  286. msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-edges", ("edges", n.Edges.Count)));
  287. msg.PushNewline();
  288. if (component.LastAnalyzerPointValue != null)
  289. msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-value", ("value", component.LastAnalyzerPointValue)));
  290. return msg;
  291. }
  292. /// <summary>
  293. /// Extracts points from the artifact and updates the server points
  294. /// </summary>
  295. /// <param name="uid"></param>
  296. /// <param name="component"></param>
  297. /// <param name="args"></param>
  298. private void OnExtractButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleExtractButtonPressedMessage args)
  299. {
  300. if (component.AnalyzerEntity == null)
  301. return;
  302. if (!_research.TryGetClientServer(uid, out var server, out var serverComponent))
  303. return;
  304. var artifact = GetArtifactForAnalysis(component.AnalyzerEntity);
  305. if (artifact == null)
  306. return;
  307. var pointValue = _artifact.GetResearchPointValue(artifact.Value);
  308. // no new nodes triggered so nothing to add
  309. if (pointValue == 0)
  310. return;
  311. _research.ModifyServerPoints(server.Value, pointValue, serverComponent);
  312. _artifact.AdjustConsumedPoints(artifact.Value, pointValue);
  313. _audio.PlayPvs(component.ExtractSound, component.AnalyzerEntity.Value, AudioParams.Default.WithVolume(2f));
  314. _popup.PopupEntity(Loc.GetString("analyzer-artifact-extract-popup"),
  315. component.AnalyzerEntity.Value, PopupType.Large);
  316. UpdateUserInterface(uid, component);
  317. }
  318. private void OnBiasButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleBiasButtonPressedMessage args)
  319. {
  320. if (component.AnalyzerEntity == null)
  321. return;
  322. if (!TryComp<TraversalDistorterComponent>(component.AnalyzerEntity, out var trav))
  323. return;
  324. if (!_traversalDistorter.SetState(component.AnalyzerEntity.Value, trav, args.IsDown))
  325. return;
  326. UpdateUserInterface(uid, component);
  327. }
  328. /// <summary>
  329. /// Cancels scans if the artifact changes nodes (is activated) during the scan.
  330. /// </summary>
  331. private void OnArtifactActivated(EntityUid uid, ActiveScannedArtifactComponent component, ArtifactActivatedEvent args)
  332. {
  333. CancelScan(uid);
  334. }
  335. /// <summary>
  336. /// Stops the current scan
  337. /// </summary>
  338. [PublicAPI]
  339. public void CancelScan(EntityUid artifact, ActiveScannedArtifactComponent? component = null, ArtifactAnalyzerComponent? analyzer = null)
  340. {
  341. if (!Resolve(artifact, ref component, false))
  342. return;
  343. if (!Resolve(component.Scanner, ref analyzer))
  344. return;
  345. _audio.PlayPvs(component.ScanFailureSound, component.Scanner, AudioParams.Default.WithVolume(3f));
  346. RemComp<ActiveArtifactAnalyzerComponent>(component.Scanner);
  347. if (analyzer.Console != null)
  348. UpdateUserInterface(analyzer.Console.Value);
  349. RemCompDeferred(artifact, component);
  350. }
  351. /// <summary>
  352. /// Finishes the current scan.
  353. /// </summary>
  354. [PublicAPI]
  355. public void FinishScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
  356. {
  357. if (!Resolve(uid, ref component, ref active))
  358. return;
  359. component.ReadyToPrint = true;
  360. _audio.PlayPvs(component.ScanFinishedSound, uid);
  361. component.LastAnalyzedArtifact = active.Artifact;
  362. UpdateAnalyzerInformation(uid, component);
  363. RemComp<ActiveScannedArtifactComponent>(active.Artifact);
  364. RemComp(uid, active);
  365. if (component.Console != null)
  366. UpdateUserInterface(component.Console.Value);
  367. }
  368. [PublicAPI]
  369. public void PauseScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
  370. {
  371. if (!Resolve(uid, ref component, ref active) || active.AnalysisPaused)
  372. return;
  373. active.AnalysisPaused = true;
  374. // As we pause, we store what was already completed.
  375. active.AccumulatedRunTime = (_timing.CurTime - active.StartTime) + active.AccumulatedRunTime;
  376. if (Exists(component.Console))
  377. UpdateUserInterface(component.Console.Value);
  378. }
  379. [PublicAPI]
  380. public void ResumeScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
  381. {
  382. if (!Resolve(uid, ref component, ref active) || !active.AnalysisPaused)
  383. return;
  384. active.StartTime = _timing.CurTime;
  385. active.AnalysisPaused = false;
  386. if (Exists(component.Console))
  387. UpdateUserInterface(component.Console.Value);
  388. }
  389. private void OnItemPlaced(EntityUid uid, ArtifactAnalyzerComponent component, ref ItemPlacedEvent args)
  390. {
  391. if (component.Console != null && Exists(component.Console))
  392. UpdateUserInterface(component.Console.Value);
  393. }
  394. private void OnItemRemoved(EntityUid uid, ArtifactAnalyzerComponent component, ref ItemRemovedEvent args)
  395. {
  396. // Scanners shouldn't give permanent remove vision to an artifact, and the scanned artifact doesn't have any
  397. // component to track analyzers that have scanned it for removal if the artifact gets deleted.
  398. // So we always clear this on removal.
  399. component.LastAnalyzedArtifact = null;
  400. // cancel the scan if the artifact moves off the analyzer
  401. CancelScan(args.OtherEntity);
  402. if (Exists(component.Console))
  403. UpdateUserInterface(component.Console.Value);
  404. }
  405. private void OnAnalyzeStart(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentStartup args)
  406. {
  407. _receiver.SetNeedsPower(uid, true);
  408. _ambientSound.SetAmbience(uid, true);
  409. }
  410. private void OnAnalyzeEnd(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentShutdown args)
  411. {
  412. _receiver.SetNeedsPower(uid, false);
  413. _ambientSound.SetAmbience(uid, false);
  414. }
  415. private void OnPowerChanged(EntityUid uid, ActiveArtifactAnalyzerComponent active, ref PowerChangedEvent args)
  416. {
  417. if (!args.Powered)
  418. {
  419. PauseScan(uid, null, active);
  420. }
  421. else
  422. {
  423. ResumeScan(uid, null, active);
  424. }
  425. }
  426. }