FaxSystem.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. using Content.Server.Administration;
  2. using Content.Server.Administration.Managers;
  3. using Content.Server.Chat.Managers;
  4. using Content.Server.DeviceNetwork;
  5. using Content.Server.DeviceNetwork.Components;
  6. using Content.Server.DeviceNetwork.Systems;
  7. using Content.Server.Labels;
  8. using Content.Server.Popups;
  9. using Content.Server.Power.Components;
  10. using Content.Server.Tools;
  11. using Content.Shared.UserInterface;
  12. using Content.Shared.Administration.Logs;
  13. using Content.Shared.Containers.ItemSlots;
  14. using Content.Shared.Database;
  15. using Content.Shared.DeviceNetwork;
  16. using Content.Shared.Emag.Components;
  17. using Content.Shared.Emag.Systems;
  18. using Content.Shared.Fax;
  19. using Content.Shared.Fax.Systems;
  20. using Content.Shared.Fax.Components;
  21. using Content.Shared.Interaction;
  22. using Content.Shared.Labels.Components;
  23. using Content.Shared.Mobs.Components;
  24. using Content.Shared.Paper;
  25. using Robust.Server.GameObjects;
  26. using Robust.Shared.Audio;
  27. using Robust.Shared.Audio.Systems;
  28. using Robust.Shared.Containers;
  29. using Robust.Shared.Player;
  30. using Robust.Shared.Prototypes;
  31. using Content.Shared.NameModifier.Components;
  32. using Content.Shared.Power;
  33. namespace Content.Server.Fax;
  34. public sealed class FaxSystem : EntitySystem
  35. {
  36. [Dependency] private readonly IChatManager _chat = default!;
  37. [Dependency] private readonly IAdminManager _adminManager = default!;
  38. [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
  39. [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
  40. [Dependency] private readonly PopupSystem _popupSystem = default!;
  41. [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
  42. [Dependency] private readonly PaperSystem _paperSystem = default!;
  43. [Dependency] private readonly LabelSystem _labelSystem = default!;
  44. [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
  45. [Dependency] private readonly ToolSystem _toolSystem = default!;
  46. [Dependency] private readonly QuickDialogSystem _quickDialog = default!;
  47. [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
  48. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  49. [Dependency] private readonly MetaDataSystem _metaData = default!;
  50. [Dependency] private readonly FaxecuteSystem _faxecute = default!;
  51. [Dependency] private readonly EmagSystem _emag = default!;
  52. private const string PaperSlotId = "Paper";
  53. public override void Initialize()
  54. {
  55. base.Initialize();
  56. // Hooks
  57. SubscribeLocalEvent<FaxMachineComponent, ComponentInit>(OnComponentInit);
  58. SubscribeLocalEvent<FaxMachineComponent, MapInitEvent>(OnMapInit);
  59. SubscribeLocalEvent<FaxMachineComponent, ComponentRemove>(OnComponentRemove);
  60. SubscribeLocalEvent<FaxMachineComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
  61. SubscribeLocalEvent<FaxMachineComponent, EntRemovedFromContainerMessage>(OnItemSlotChanged);
  62. SubscribeLocalEvent<FaxMachineComponent, PowerChangedEvent>(OnPowerChanged);
  63. SubscribeLocalEvent<FaxMachineComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
  64. // Interaction
  65. SubscribeLocalEvent<FaxMachineComponent, InteractUsingEvent>(OnInteractUsing);
  66. SubscribeLocalEvent<FaxMachineComponent, GotEmaggedEvent>(OnEmagged);
  67. // UI
  68. SubscribeLocalEvent<FaxMachineComponent, AfterActivatableUIOpenEvent>(OnToggleInterface);
  69. SubscribeLocalEvent<FaxMachineComponent, FaxFileMessage>(OnFileButtonPressed);
  70. SubscribeLocalEvent<FaxMachineComponent, FaxCopyMessage>(OnCopyButtonPressed);
  71. SubscribeLocalEvent<FaxMachineComponent, FaxSendMessage>(OnSendButtonPressed);
  72. SubscribeLocalEvent<FaxMachineComponent, FaxRefreshMessage>(OnRefreshButtonPressed);
  73. SubscribeLocalEvent<FaxMachineComponent, FaxDestinationMessage>(OnDestinationSelected);
  74. }
  75. public override void Update(float frameTime)
  76. {
  77. base.Update(frameTime);
  78. var query = EntityQueryEnumerator<FaxMachineComponent, ApcPowerReceiverComponent>();
  79. while (query.MoveNext(out var uid, out var fax, out var receiver))
  80. {
  81. if (!receiver.Powered)
  82. continue;
  83. ProcessPrintingAnimation(uid, frameTime, fax);
  84. ProcessInsertingAnimation(uid, frameTime, fax);
  85. ProcessSendingTimeout(uid, frameTime, fax);
  86. }
  87. }
  88. private void ProcessPrintingAnimation(EntityUid uid, float frameTime, FaxMachineComponent comp)
  89. {
  90. if (comp.PrintingTimeRemaining > 0)
  91. {
  92. comp.PrintingTimeRemaining -= frameTime;
  93. UpdateAppearance(uid, comp);
  94. var isAnimationEnd = comp.PrintingTimeRemaining <= 0;
  95. if (isAnimationEnd)
  96. {
  97. SpawnPaperFromQueue(uid, comp);
  98. UpdateUserInterface(uid, comp);
  99. }
  100. return;
  101. }
  102. if (comp.PrintingQueue.Count > 0)
  103. {
  104. comp.PrintingTimeRemaining = comp.PrintingTime;
  105. _audioSystem.PlayPvs(comp.PrintSound, uid);
  106. }
  107. }
  108. private void ProcessInsertingAnimation(EntityUid uid, float frameTime, FaxMachineComponent comp)
  109. {
  110. if (comp.InsertingTimeRemaining <= 0)
  111. return;
  112. comp.InsertingTimeRemaining -= frameTime;
  113. UpdateAppearance(uid, comp);
  114. var isAnimationEnd = comp.InsertingTimeRemaining <= 0;
  115. if (isAnimationEnd)
  116. {
  117. _itemSlotsSystem.SetLock(uid, comp.PaperSlot, false);
  118. UpdateUserInterface(uid, comp);
  119. }
  120. }
  121. private void ProcessSendingTimeout(EntityUid uid, float frameTime, FaxMachineComponent comp)
  122. {
  123. if (comp.SendTimeoutRemaining > 0)
  124. {
  125. comp.SendTimeoutRemaining -= frameTime;
  126. if (comp.SendTimeoutRemaining <= 0)
  127. UpdateUserInterface(uid, comp);
  128. }
  129. }
  130. private void OnComponentInit(EntityUid uid, FaxMachineComponent component, ComponentInit args)
  131. {
  132. _itemSlotsSystem.AddItemSlot(uid, PaperSlotId, component.PaperSlot);
  133. UpdateAppearance(uid, component);
  134. }
  135. private void OnComponentRemove(EntityUid uid, FaxMachineComponent component, ComponentRemove args)
  136. {
  137. _itemSlotsSystem.RemoveItemSlot(uid, component.PaperSlot);
  138. }
  139. private void OnMapInit(EntityUid uid, FaxMachineComponent component, MapInitEvent args)
  140. {
  141. // Load all faxes on map in cache each other to prevent taking same name by user created fax
  142. Refresh(uid, component);
  143. }
  144. private void OnItemSlotChanged(EntityUid uid, FaxMachineComponent component, ContainerModifiedMessage args)
  145. {
  146. if (!component.Initialized)
  147. return;
  148. if (args.Container.ID != component.PaperSlot.ID)
  149. return;
  150. var isPaperInserted = component.PaperSlot.Item.HasValue;
  151. if (isPaperInserted)
  152. {
  153. component.InsertingTimeRemaining = component.InsertionTime;
  154. _itemSlotsSystem.SetLock(uid, component.PaperSlot, true);
  155. }
  156. UpdateUserInterface(uid, component);
  157. }
  158. private void OnPowerChanged(EntityUid uid, FaxMachineComponent component, ref PowerChangedEvent args)
  159. {
  160. var isInsertInterrupted = !args.Powered && component.InsertingTimeRemaining > 0;
  161. if (isInsertInterrupted)
  162. {
  163. component.InsertingTimeRemaining = 0f; // Reset animation
  164. // Drop from slot because animation did not play completely
  165. _itemSlotsSystem.SetLock(uid, component.PaperSlot, false);
  166. _itemSlotsSystem.TryEject(uid, component.PaperSlot, null, out var _, true);
  167. }
  168. var isPrintInterrupted = !args.Powered && component.PrintingTimeRemaining > 0;
  169. if (isPrintInterrupted)
  170. {
  171. component.PrintingTimeRemaining = 0f; // Reset animation
  172. }
  173. if (isInsertInterrupted || isPrintInterrupted)
  174. UpdateAppearance(uid, component);
  175. _itemSlotsSystem.SetLock(uid, component.PaperSlot, !args.Powered); // Lock slot when power is off
  176. }
  177. private void OnInteractUsing(EntityUid uid, FaxMachineComponent component, InteractUsingEvent args)
  178. {
  179. if (args.Handled ||
  180. !TryComp<ActorComponent>(args.User, out var actor) ||
  181. !_toolSystem.HasQuality(args.Used, "Screwing")) // Screwing because Pulsing already used by device linking
  182. return;
  183. _quickDialog.OpenDialog(actor.PlayerSession,
  184. Loc.GetString("fax-machine-dialog-rename"),
  185. Loc.GetString("fax-machine-dialog-field-name"),
  186. (string newName) =>
  187. {
  188. if (component.FaxName == newName)
  189. return;
  190. if (newName.Length > 20)
  191. {
  192. _popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-long"), uid);
  193. return;
  194. }
  195. if (component.KnownFaxes.ContainsValue(newName) && !_emag.CheckFlag(uid, EmagType.Interaction)) // Allow existing names if emagged for fun
  196. {
  197. _popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-exist"), uid);
  198. return;
  199. }
  200. _adminLogger.Add(LogType.Action,
  201. LogImpact.Low,
  202. $"{ToPrettyString(args.User):user} renamed {ToPrettyString(uid):tool} from \"{component.FaxName}\" to \"{newName}\"");
  203. component.FaxName = newName;
  204. _popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-set"), uid);
  205. UpdateUserInterface(uid, component);
  206. });
  207. args.Handled = true;
  208. }
  209. private void OnEmagged(EntityUid uid, FaxMachineComponent component, ref GotEmaggedEvent args)
  210. {
  211. if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
  212. return;
  213. if (_emag.CheckFlag(uid, EmagType.Interaction))
  214. return;
  215. args.Handled = true;
  216. }
  217. private void OnPacketReceived(EntityUid uid, FaxMachineComponent component, DeviceNetworkPacketEvent args)
  218. {
  219. if (!HasComp<DeviceNetworkComponent>(uid) || string.IsNullOrEmpty(args.SenderAddress))
  220. return;
  221. if (args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
  222. {
  223. switch (command)
  224. {
  225. case FaxConstants.FaxPingCommand:
  226. var isForSyndie = _emag.CheckFlag(uid, EmagType.Interaction) &&
  227. args.Data.ContainsKey(FaxConstants.FaxSyndicateData);
  228. if (!isForSyndie && !component.ResponsePings)
  229. return;
  230. var payload = new NetworkPayload()
  231. {
  232. { DeviceNetworkConstants.Command, FaxConstants.FaxPongCommand },
  233. { FaxConstants.FaxNameData, component.FaxName }
  234. };
  235. _deviceNetworkSystem.QueuePacket(uid, args.SenderAddress, payload);
  236. break;
  237. case FaxConstants.FaxPongCommand:
  238. if (!args.Data.TryGetValue(FaxConstants.FaxNameData, out string? faxName))
  239. return;
  240. component.KnownFaxes[args.SenderAddress] = faxName;
  241. UpdateUserInterface(uid, component);
  242. break;
  243. case FaxConstants.FaxPrintCommand:
  244. if (!args.Data.TryGetValue(FaxConstants.FaxPaperNameData, out string? name) ||
  245. !args.Data.TryGetValue(FaxConstants.FaxPaperContentData, out string? content))
  246. return;
  247. args.Data.TryGetValue(FaxConstants.FaxPaperLabelData, out string? label);
  248. args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState);
  249. args.Data.TryGetValue(FaxConstants.FaxPaperStampedByData, out List<StampDisplayInfo>? stampedBy);
  250. args.Data.TryGetValue(FaxConstants.FaxPaperPrototypeData, out string? prototypeId);
  251. args.Data.TryGetValue(FaxConstants.FaxPaperLockedData, out bool? locked);
  252. var printout = new FaxPrintout(content, name, label, prototypeId, stampState, stampedBy, locked ?? false);
  253. Receive(uid, printout, args.SenderAddress);
  254. break;
  255. }
  256. }
  257. }
  258. private void OnToggleInterface(EntityUid uid, FaxMachineComponent component, AfterActivatableUIOpenEvent args)
  259. {
  260. UpdateUserInterface(uid, component);
  261. }
  262. private void OnFileButtonPressed(EntityUid uid, FaxMachineComponent component, FaxFileMessage args)
  263. {
  264. args.Label = args.Label?[..Math.Min(args.Label.Length, FaxFileMessageValidation.MaxLabelSize)];
  265. args.Content = args.Content[..Math.Min(args.Content.Length, FaxFileMessageValidation.MaxContentSize)];
  266. PrintFile(uid, component, args);
  267. }
  268. private void OnCopyButtonPressed(EntityUid uid, FaxMachineComponent component, FaxCopyMessage args)
  269. {
  270. if (HasComp<MobStateComponent>(component.PaperSlot.Item))
  271. _faxecute.Faxecute(uid, component); // when button pressed it will hurt the mob.
  272. else
  273. Copy(uid, component, args);
  274. }
  275. private void OnSendButtonPressed(EntityUid uid, FaxMachineComponent component, FaxSendMessage args)
  276. {
  277. if (HasComp<MobStateComponent>(component.PaperSlot.Item))
  278. _faxecute.Faxecute(uid, component); // when button pressed it will hurt the mob.
  279. else
  280. Send(uid, component, args);
  281. }
  282. private void OnRefreshButtonPressed(EntityUid uid, FaxMachineComponent component, FaxRefreshMessage args)
  283. {
  284. Refresh(uid, component);
  285. }
  286. private void OnDestinationSelected(EntityUid uid, FaxMachineComponent component, FaxDestinationMessage args)
  287. {
  288. SetDestination(uid, args.Address, component);
  289. }
  290. private void UpdateAppearance(EntityUid uid, FaxMachineComponent? component = null)
  291. {
  292. if (!Resolve(uid, ref component))
  293. return;
  294. if (TryComp<FaxableObjectComponent>(component.PaperSlot.Item, out var faxable))
  295. component.InsertingState = faxable.InsertingState;
  296. if (component.InsertingTimeRemaining > 0)
  297. {
  298. _appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Inserting);
  299. Dirty(uid, component);
  300. }
  301. else if (component.PrintingTimeRemaining > 0)
  302. _appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Printing);
  303. else
  304. _appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Normal);
  305. }
  306. private void UpdateUserInterface(EntityUid uid, FaxMachineComponent? component = null)
  307. {
  308. if (!Resolve(uid, ref component))
  309. return;
  310. var isPaperInserted = component.PaperSlot.Item != null;
  311. var canSend = isPaperInserted &&
  312. component.DestinationFaxAddress != null &&
  313. component.SendTimeoutRemaining <= 0 &&
  314. component.InsertingTimeRemaining <= 0;
  315. var canCopy = isPaperInserted &&
  316. component.SendTimeoutRemaining <= 0 &&
  317. component.InsertingTimeRemaining <= 0;
  318. var state = new FaxUiState(component.FaxName, component.KnownFaxes, canSend, canCopy, isPaperInserted, component.DestinationFaxAddress);
  319. _userInterface.SetUiState(uid, FaxUiKey.Key, state);
  320. }
  321. /// <summary>
  322. /// Set fax destination address not checking if he knows it exists
  323. /// </summary>
  324. public void SetDestination(EntityUid uid, string destAddress, FaxMachineComponent? component = null)
  325. {
  326. if (!Resolve(uid, ref component))
  327. return;
  328. component.DestinationFaxAddress = destAddress;
  329. UpdateUserInterface(uid, component);
  330. }
  331. /// <summary>
  332. /// Clears current known fax info and make network scan ping
  333. /// Adds special data to payload if it was emagged to identify itself as a Syndicate
  334. /// </summary>
  335. public void Refresh(EntityUid uid, FaxMachineComponent? component = null)
  336. {
  337. if (!Resolve(uid, ref component))
  338. return;
  339. component.DestinationFaxAddress = null;
  340. component.KnownFaxes.Clear();
  341. var payload = new NetworkPayload()
  342. {
  343. { DeviceNetworkConstants.Command, FaxConstants.FaxPingCommand }
  344. };
  345. if (_emag.CheckFlag(uid, EmagType.Interaction))
  346. payload.Add(FaxConstants.FaxSyndicateData, true);
  347. _deviceNetworkSystem.QueuePacket(uid, null, payload);
  348. }
  349. /// <summary>
  350. /// Makes fax print from a file from the computer. A timeout is set after copying,
  351. /// which is shared by the send button.
  352. /// </summary>
  353. public void PrintFile(EntityUid uid, FaxMachineComponent component, FaxFileMessage args)
  354. {
  355. var prototype = args.OfficePaper ? component.PrintOfficePaperId : component.PrintPaperId;
  356. var name = Loc.GetString("fax-machine-printed-paper-name");
  357. var printout = new FaxPrintout(args.Content, name, args.Label, prototype);
  358. component.PrintingQueue.Enqueue(printout);
  359. component.SendTimeoutRemaining += component.SendTimeout;
  360. UpdateUserInterface(uid, component);
  361. // Unfortunately, since a paper entity does not yet exist, we have to emulate what LabelSystem will do.
  362. var nameWithLabel = (args.Label is { } label) ? $"{name} ({label})" : name;
  363. _adminLogger.Add(LogType.Action,
  364. LogImpact.Low,
  365. $"{ToPrettyString(args.Actor):actor} " +
  366. $"added print job to \"{component.FaxName}\" {ToPrettyString(uid):tool} " +
  367. $"of {nameWithLabel}: {args.Content}");
  368. }
  369. /// <summary>
  370. /// Copies the paper in the fax. A timeout is set after copying,
  371. /// which is shared by the send button.
  372. /// </summary>
  373. public void Copy(EntityUid uid, FaxMachineComponent? component, FaxCopyMessage args)
  374. {
  375. if (!Resolve(uid, ref component))
  376. return;
  377. if (component.SendTimeoutRemaining > 0)
  378. return;
  379. var sendEntity = component.PaperSlot.Item;
  380. if (sendEntity == null)
  381. return;
  382. if (!TryComp(sendEntity, out MetaDataComponent? metadata) ||
  383. !TryComp<PaperComponent>(sendEntity, out var paper))
  384. return;
  385. TryComp<LabelComponent>(sendEntity, out var labelComponent);
  386. TryComp<NameModifierComponent>(sendEntity, out var nameMod);
  387. // TODO: See comment in 'Send()' about not being able to copy whole entities
  388. var printout = new FaxPrintout(paper.Content,
  389. nameMod?.BaseName ?? metadata.EntityName,
  390. labelComponent?.CurrentLabel,
  391. metadata.EntityPrototype?.ID ?? component.PrintPaperId,
  392. paper.StampState,
  393. paper.StampedBy,
  394. paper.EditingDisabled);
  395. component.PrintingQueue.Enqueue(printout);
  396. component.SendTimeoutRemaining += component.SendTimeout;
  397. // Don't play component.SendSound - it clashes with the printing sound, which
  398. // will start immediately.
  399. UpdateUserInterface(uid, component);
  400. _adminLogger.Add(LogType.Action,
  401. LogImpact.Low,
  402. $"{ToPrettyString(args.Actor):actor} " +
  403. $"added copy job to \"{component.FaxName}\" {ToPrettyString(uid):tool} " +
  404. $"of {ToPrettyString(sendEntity):subject}: {printout.Content}");
  405. }
  406. /// <summary>
  407. /// Sends message to addressee if paper is set and a known fax is selected
  408. /// A timeout is set after sending, which is shared by the copy button.
  409. /// </summary>
  410. public void Send(EntityUid uid, FaxMachineComponent? component, FaxSendMessage args)
  411. {
  412. if (!Resolve(uid, ref component))
  413. return;
  414. if (component.SendTimeoutRemaining > 0)
  415. return;
  416. var sendEntity = component.PaperSlot.Item;
  417. if (sendEntity == null)
  418. return;
  419. if (component.DestinationFaxAddress == null)
  420. return;
  421. if (!component.KnownFaxes.TryGetValue(component.DestinationFaxAddress, out var faxName))
  422. return;
  423. if (!TryComp(sendEntity, out MetaDataComponent? metadata) ||
  424. !TryComp<PaperComponent>(sendEntity, out var paper))
  425. return;
  426. TryComp<NameModifierComponent>(sendEntity, out var nameMod);
  427. TryComp<LabelComponent>(sendEntity, out var labelComponent);
  428. var payload = new NetworkPayload()
  429. {
  430. { DeviceNetworkConstants.Command, FaxConstants.FaxPrintCommand },
  431. { FaxConstants.FaxPaperNameData, nameMod?.BaseName ?? metadata.EntityName },
  432. { FaxConstants.FaxPaperLabelData, labelComponent?.CurrentLabel },
  433. { FaxConstants.FaxPaperContentData, paper.Content },
  434. { FaxConstants.FaxPaperLockedData, paper.EditingDisabled },
  435. };
  436. if (metadata.EntityPrototype != null)
  437. {
  438. // TODO: Ideally, we could just make a copy of the whole entity when it's
  439. // faxed, in order to preserve visuals, etc.. This functionality isn't
  440. // available yet, so we'll pass along the originating prototypeId and fall
  441. // back to component.PrintPaperId in SpawnPaperFromQueue if we can't find one here.
  442. payload[FaxConstants.FaxPaperPrototypeData] = metadata.EntityPrototype.ID;
  443. }
  444. if (paper.StampState != null)
  445. {
  446. payload[FaxConstants.FaxPaperStampStateData] = paper.StampState;
  447. payload[FaxConstants.FaxPaperStampedByData] = paper.StampedBy;
  448. }
  449. _deviceNetworkSystem.QueuePacket(uid, component.DestinationFaxAddress, payload);
  450. _adminLogger.Add(LogType.Action,
  451. LogImpact.Low,
  452. $"{ToPrettyString(args.Actor):actor} " +
  453. $"sent fax from \"{component.FaxName}\" {ToPrettyString(uid):tool} " +
  454. $"to \"{faxName}\" ({component.DestinationFaxAddress}) " +
  455. $"of {ToPrettyString(sendEntity):subject}: {paper.Content}");
  456. component.SendTimeoutRemaining += component.SendTimeout;
  457. _audioSystem.PlayPvs(component.SendSound, uid);
  458. UpdateUserInterface(uid, component);
  459. }
  460. /// <summary>
  461. /// Accepts a new message and adds it to the queue to print
  462. /// If has parameter "notifyAdmins" also output a special message to admin chat.
  463. /// </summary>
  464. public void Receive(EntityUid uid, FaxPrintout printout, string? fromAddress = null, FaxMachineComponent? component = null)
  465. {
  466. if (!Resolve(uid, ref component))
  467. return;
  468. var faxName = Loc.GetString("fax-machine-popup-source-unknown");
  469. if (fromAddress != null && component.KnownFaxes.TryGetValue(fromAddress, out var fax)) // If message received from unknown fax address
  470. faxName = fax;
  471. _popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-received", ("from", faxName)), uid);
  472. _appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Printing);
  473. if (component.NotifyAdmins)
  474. NotifyAdmins(faxName);
  475. component.PrintingQueue.Enqueue(printout);
  476. }
  477. private void SpawnPaperFromQueue(EntityUid uid, FaxMachineComponent? component = null)
  478. {
  479. if (!Resolve(uid, ref component) || component.PrintingQueue.Count == 0)
  480. return;
  481. var printout = component.PrintingQueue.Dequeue();
  482. var entityToSpawn = printout.PrototypeId.Length == 0 ? component.PrintPaperId.ToString() : printout.PrototypeId;
  483. var printed = EntityManager.SpawnEntity(entityToSpawn, Transform(uid).Coordinates);
  484. if (TryComp<PaperComponent>(printed, out var paper))
  485. {
  486. _paperSystem.SetContent((printed, paper), printout.Content);
  487. // Apply stamps
  488. if (printout.StampState != null)
  489. {
  490. foreach (var stamp in printout.StampedBy)
  491. {
  492. _paperSystem.TryStamp((printed, paper), stamp, printout.StampState);
  493. }
  494. }
  495. paper.EditingDisabled = printout.Locked;
  496. }
  497. _metaData.SetEntityName(printed, printout.Name);
  498. if (printout.Label is { } label)
  499. {
  500. _labelSystem.Label(printed, label);
  501. }
  502. _adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid):tool} printed {ToPrettyString(printed):subject}: {printout.Content}");
  503. }
  504. private void NotifyAdmins(string faxName)
  505. {
  506. _chat.SendAdminAnnouncement(Loc.GetString("fax-machine-chat-notify", ("fax", faxName)));
  507. _audioSystem.PlayGlobal("/Audio/Machines/high_tech_confirm.ogg", Filter.Empty().AddPlayers(_adminManager.ActiveAdmins), false, AudioParams.Default.WithVolume(-8f));
  508. }
  509. }