Files
Delta-v/Content.Server/Fax/FaxSystem.cs
T
29e55b9464 Upstream Merge (June / July) attempt 2 electric boogaloo (#4607)
* Fix the sensor monitoring console forcing a GC every 3 seconds (#38146)

* Optimize sensor monitoring window graph drawing

* Add shared static Vector2 pool for all GraphView instances

* Address requested changes

* remove lock

* Update submodule to 264.0.0 (#38629)

* Toy/Plushie Inhands and Wearables (#38514)

* Fixed dependency injection and some other issues in a few places.

* More issue fixes

* patchfor broken NetworkConfiguratorLinkMenu (#38632)

fix

* Fix solutions flickering when transferring contents (#34838)

* Use Solution clones when applying SolutionComponent states

* Revert "Use Solution clones when applying SolutionComponent states"

This reverts commit 013fd111cf92b22562e00f98a7aaa49bc4b4ed62.

* Make Solution implement ICloneable and rename Clone method.

* Copy CanReact value when cloning a Solution

* Convert to IRobustCloneable

* Fix typos in guidebook: Buisness → Business (#38636)

* Fix typo in `MinorAntagonists.xml`

Buisness → Business

* Fix typo in `YourFirstCharacter.xml`

Buisness → Business

* Various Headphones Fixes and Tweaks (#38479)

* sprites, wearables

* neck

* icon-on sprite

* Cleanup prototype instantiation in `DamageTest` (#38639)

Cleanup prototype instantiation in DamageTest

* Cleanup prototype instantiation in `ExplosionSystem` (#38642)

Cleanup prototype instantiation in ExplosionSystem

* AddBodyPartCommand localization. (#38612)

commit

* fix ItemSlotsSystem debug assert (#38655)

* Allow the Command & Super door remotes to use the access of their user. (Re-creation of PR due to changes to game balance) (#35536)

* Added directional beacons (#38284)

* Added directional beacons

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Fixed names

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

---------

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Power stat and nuke codes commands get some LEC love. (#38585)

* commit

* requested changes.

* Dsay Dirty and Follow commands converted to LEC and localized. (#38666)

* commit

* whoopwhoopwhoop

* Retro laser sprite fix (#38676)

* Fixed everything except the icon

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* fixed icon

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

---------

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* fix water coolers (#38681)

* Monochromacy typo fix (#38686)

* fixes the typo

* Fixed cloning looking for the trait, not the component, RE https://github.com/space-wizards/space-station-14/pull/38686#issuecomment-3025093504

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Validate `CloningSettingsPrototype`s (#38688)

* Validate CloningSettingsPrototypes

* Update Content.IntegrationTests/Tests/Cloning/CloningSettingsPrototypeTest.cs

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Check EventComponents too

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Add test of objective-related console commands (#36400)

* Add test of objective add/list/remove commands

* Not sure why we're validating test prototypes, but sure

* We don't need a map

* Improvements and fixups for New Status Effect API (#38660)

* Predict healing and bloodstream (#38690)

* initial commit

* reapply 38126

* fix rootable

* someone missed an important minus sign here

* try this

* fix

* fix

* reenable crit hits

* cleanup

* fix status time dirtying

* fix

* camelCase

* Healing and bloodstream prediction fixes.

* Resolving Wizard casting recall on nuke disk making it impossible to disarm (#38661)

* Resolving Wizard Recall on Nuke disk making it impossible to disarm
- Adding a DisarmBomb case to nuke status update loop
- Changing a few methods and parameters to properly follow formatting standards
- Updating some names to follow camelCase

* Updating missed tag

* Reverting DataField change

Should prevent this preventative bugfix being a breaking change.

* Switch HSV to the default colorspace for character customization (#38434)

* Made HSV default for character editor

* Adds/fixes comments to HSV defaulting

* Added dropbox fix, potentially cursed

* Revert "Added dropbox fix, potentially cursed"

This reverts commit a709883366fbee813e839742125e70844672af29.

---------

Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com>

* Fix: Don't deploy foldables when clicking on items inside containers (#38709)

* Fix

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Pressure Relief Valve (#36708)

* initial system (this math is probably WRONG)

* General code cleanup and OnExamined support
(holy moly this code sucks)

* UICode and related events foundation
TODO:
- Actually write the XAML UI and the underlying system
- Un-shitcode the entire thing
- Actually test everything...

* Working UI code
TODO: Make predicted, as this certainly isn't predicted. Even though I said it was. It isn't.

* Remove one TODO for unshitcoding the examine code

* Add reminder
yea

* Make predicted (defenitely isn't)
(also defenitely isn't a copypaste from pressure pump code)

* It's predicted!
TODO:
- Give it snazzy predicted visuals!
- Have a different field for pressure entry, lest it gets bulldozed every UI update.

* Improve gas pressure relief valve UI
TODO: Reminder to reduce amount of dirties using deltafields

* Implement DirtyField prediction

* Entity<T> cleanup
A lot of Entity<T> conversions and lukewarm cleanup.

Also got caught copy pasting code in 4K UHD but it's not like you couldn't tell.

* More cleanup and comments

* Remove TODO comment on bulldozing window title

* """refactoring"""
- Move appearance out of shared and finally fix it. Pointless to predict appearance in this instance.
- More Entity<T> conversions because I like them.
- Move UI creation handling over entirely to the ActivatableUI system.
- Fix a hardcoded locale string (why????).

* Add visuals

* Revert debugging variable replacememt
yea

* Revert skissue

* Remove unused using directives and remove TODO

* Localize, cleanup, document

* Fix adminlogging discrepancy

* Add ability to construct, add guidebook entry

* Clear up comment

* Add guidebook tooltip to valve

* Convert GasPressureReliefValveBoundUserInterface declaration into primary constructor

* Adds more input handling and adds autofill on open

* Un-deepfry input validator shitcode
Genuinely what was I smoking

* improve visuals logic

* Refactor again
- Update math to the correct implementation
- Moved code that could be re-used in the future into a helper method under AtmosphereSystem.Gases.cs

* I'm sorry but I hate warnings

* Remove unused using directive in AtmosphereSystem.Gases.cs

* Review and cleanup

* Lukewarm UI glossup

* Maintainer for the upstream project btw

* Remove redundant state sets and messy logic

* Unduplicate valve updater code

* Redo UI (im sorry Slarti)

* run tests

* Test refactored UI messaging

* Second round of UI improvements
- God please find a way to improve this system. Feels bad.

* Update loop implementation

* Further predict UI

* Clear up SetToCurrentThreshold

* cleanup

* Update to master + pipe layers and bug fixes
want to run tests

* fixes

* Deploy rename pipebomb

* Documentation and requested changes

* Rename the method that wiggled away

* Undo rounding changes

* Fix comment

* Rename and cleanup

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Fix TextLinkTag (#32203)

* Adjust uplink buy button to be under item icon (#38596)

* Adjusted uplink buy button to be under item icon

* Put the discount subtext under the icon

* Indent fixes, added margin

---------

Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com>

* Scurrets - Audio Improvements (#38482)

* Scurret audio tuning

* Add new sfx

* Update sneezing sfx

* YAML support

* Rename a folder

* make telesci wreck easier (#37569)

rel

* Validate `ShaderPrototype` IDs (#38728)

* Convert all shader prototype string literals to protoids in overlays

* Convert more shader prototype literal strings to protoids

* Convert ValidatePrototypeId to ProtoId

* Later

* [BUGFIX] Fixed revenant malfunction ability not working properly only MediBots and Stasis bed (#38664)

* fixed

* clean up

* orks fix smart

* review fix 1

* more requested changes

* less cursed

* more descriptive description

* better wording

* Tiny Tiny Cleanup of the EyeClosingSystem. (#38734)

Update EyeClosingSystem.cs

* Fix human skin tone distribution (#38701)

* fix: Use PredictedQueueDel for gib spell (#38729)

* fix: don't default to uncharged sprite state for cells (#38730)

* Fix Hristov description - remove inaccurate technical specs (#38746)

- Removes inaccurate 'armor piercing 14.5mm shells' reference
- Replaces it with a more funny description, matching the style of the other snipers and guns
- Keeps ammunition type '.60 anti-materiel ammo' specification
- Fixes issue #38590

Co-authored-by: Arthur Fiorese de Andrade <aandrade@cmcxs.gov.br>

* feat: allow mopping evaporating puddles (#38743)

* Validate remaining `ProtoId` strings (#38747)

Validate remaining ProtoId strings

* Validate `ProtoId`s in tests (#38745)

* Convert string literals to protoids in Content.Tests

* Convert string literals to protoids or consts in Content.IntegrationTests

* Fix linter failures
Tricksy static using misled me

* Cleanup warning in StomachSystem (#38748)

you did not see this

* Vox scars (#38592)

* Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl

* Revert "Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl"

This reverts commit c73da55ba3b39ddf93b493aecd85604c54dd8a15.

* locale key fix

* Changed top surgery scar names to be more generalized

* Adjusted face scars

* Formatting fixes

---------

Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com>

* UnlockNode command to LEC. (#38751)

* commit

* Update UnlockNodeCommand.cs

* commit

* move command locale to its own file.

* Update Content.Server/Xenoarchaeology/Artifact/UnlockNodeCommand.cs

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Typofixes for figurine dialogue (#38737)

* Typofixes for figurine dialogue

* Forgot two

* Janitor Tool: Wire Brush (#38667)

* Wow! It's -brush-

* spacing.

* Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* fixed changes

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Minor escape menu UX improvements (#38650)

* fix: spellbooks can have infinite charges (#38376)

* fix: spellbooks can have infinite charges

* refactor: indicate infinite spellbook charges with null

Not sure if I like this much better...
2025-11-06 18:22:39 +00:00

637 lines
25 KiB
C#

using Content.Server.Administration;
using Content.Server.Administration.Managers;
using Content.Server.Chat.Managers;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.Tools;
using Content.Shared.Administration.Logs;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Emag.Systems;
using Content.Shared.Fax;
using Content.Shared.Fax.Components;
using Content.Shared.Fax.Systems;
using Content.Shared.Interaction;
using Content.Shared.Labels.Components;
using Content.Shared.Labels.EntitySystems;
using Content.Shared.Mobs.Components;
using Content.Shared.NameModifier.Components;
using Content.Shared.Paper;
using Content.Shared.Power;
using Content.Shared.Tools;
using Content.Shared.UserInterface;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Fax;
public sealed class FaxSystem : EntitySystem
{
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private readonly PaperSystem _paperSystem = default!;
[Dependency] private readonly LabelSystem _labelSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly ToolSystem _toolSystem = default!;
[Dependency] private readonly QuickDialogSystem _quickDialog = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly FaxecuteSystem _faxecute = default!;
[Dependency] private readonly EmagSystem _emag = default!;
private static readonly ProtoId<ToolQualityPrototype> ScrewingQuality = "Screwing";
private const string PaperSlotId = "Paper";
public override void Initialize()
{
base.Initialize();
// Hooks
SubscribeLocalEvent<FaxMachineComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<FaxMachineComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<FaxMachineComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<FaxMachineComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
SubscribeLocalEvent<FaxMachineComponent, EntRemovedFromContainerMessage>(OnItemSlotChanged);
SubscribeLocalEvent<FaxMachineComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<FaxMachineComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
// Interaction
SubscribeLocalEvent<FaxMachineComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<FaxMachineComponent, GotEmaggedEvent>(OnEmagged);
// UI
SubscribeLocalEvent<FaxMachineComponent, AfterActivatableUIOpenEvent>(OnToggleInterface);
SubscribeLocalEvent<FaxMachineComponent, FaxFileMessage>(OnFileButtonPressed);
SubscribeLocalEvent<FaxMachineComponent, FaxCopyMessage>(OnCopyButtonPressed);
SubscribeLocalEvent<FaxMachineComponent, FaxSendMessage>(OnSendButtonPressed);
SubscribeLocalEvent<FaxMachineComponent, FaxRefreshMessage>(OnRefreshButtonPressed);
SubscribeLocalEvent<FaxMachineComponent, FaxDestinationMessage>(OnDestinationSelected);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<FaxMachineComponent, ApcPowerReceiverComponent>();
while (query.MoveNext(out var uid, out var fax, out var receiver))
{
if (!receiver.Powered)
continue;
ProcessPrintingAnimation(uid, frameTime, fax);
ProcessInsertingAnimation(uid, frameTime, fax);
ProcessSendingTimeout(uid, frameTime, fax);
}
}
private void ProcessPrintingAnimation(EntityUid uid, float frameTime, FaxMachineComponent comp)
{
if (comp.PrintingTimeRemaining > 0)
{
comp.PrintingTimeRemaining -= frameTime;
UpdateAppearance(uid, comp);
var isAnimationEnd = comp.PrintingTimeRemaining <= 0;
if (isAnimationEnd)
{
SpawnPaperFromQueue(uid, comp);
UpdateUserInterface(uid, comp);
}
return;
}
if (comp.PrintingQueue.Count > 0)
{
comp.PrintingTimeRemaining = comp.PrintingTime;
_audioSystem.PlayPvs(comp.PrintSound, uid);
}
}
private void ProcessInsertingAnimation(EntityUid uid, float frameTime, FaxMachineComponent comp)
{
if (comp.InsertingTimeRemaining <= 0)
return;
comp.InsertingTimeRemaining -= frameTime;
UpdateAppearance(uid, comp);
var isAnimationEnd = comp.InsertingTimeRemaining <= 0;
if (isAnimationEnd)
{
_itemSlotsSystem.SetLock(uid, comp.PaperSlot, false);
UpdateUserInterface(uid, comp);
}
}
private void ProcessSendingTimeout(EntityUid uid, float frameTime, FaxMachineComponent comp)
{
if (comp.SendTimeoutRemaining > 0)
{
comp.SendTimeoutRemaining -= frameTime;
if (comp.SendTimeoutRemaining <= 0)
UpdateUserInterface(uid, comp);
}
}
private void OnComponentInit(EntityUid uid, FaxMachineComponent component, ComponentInit args)
{
// <Goobstation> - define the slot in ItemSlots instead of adding it
if (_itemSlotsSystem.TryGetSlot(uid, PaperSlotId, out var slot))
component.PaperSlot = slot;
else
_itemSlotsSystem.AddItemSlot(uid, PaperSlotId, component.PaperSlot);
// </Goobstation>
UpdateAppearance(uid, component);
}
private void OnComponentRemove(EntityUid uid, FaxMachineComponent component, ComponentRemove args)
{
_itemSlotsSystem.RemoveItemSlot(uid, component.PaperSlot);
}
private void OnMapInit(EntityUid uid, FaxMachineComponent component, MapInitEvent args)
{
// Load all faxes on map in cache each other to prevent taking same name by user created fax
Refresh(uid, component);
}
private void OnItemSlotChanged(EntityUid uid, FaxMachineComponent component, ContainerModifiedMessage args)
{
if (!component.Initialized)
return;
if (args.Container.ID != component.PaperSlot.ID)
return;
var isPaperInserted = component.PaperSlot.Item.HasValue;
if (isPaperInserted)
{
component.InsertingTimeRemaining = component.InsertionTime;
_itemSlotsSystem.SetLock(uid, component.PaperSlot, true);
}
UpdateUserInterface(uid, component);
}
private void OnPowerChanged(EntityUid uid, FaxMachineComponent component, ref PowerChangedEvent args)
{
var isInsertInterrupted = !args.Powered && component.InsertingTimeRemaining > 0;
if (isInsertInterrupted)
{
component.InsertingTimeRemaining = 0f; // Reset animation
// Drop from slot because animation did not play completely
_itemSlotsSystem.SetLock(uid, component.PaperSlot, false);
_itemSlotsSystem.TryEject(uid, component.PaperSlot, null, out var _, true);
}
var isPrintInterrupted = !args.Powered && component.PrintingTimeRemaining > 0;
if (isPrintInterrupted)
{
component.PrintingTimeRemaining = 0f; // Reset animation
}
if (isInsertInterrupted || isPrintInterrupted)
UpdateAppearance(uid, component);
_itemSlotsSystem.SetLock(uid, component.PaperSlot, !args.Powered); // Lock slot when power is off
}
private void OnInteractUsing(EntityUid uid, FaxMachineComponent component, InteractUsingEvent args)
{
if (args.Handled ||
!TryComp<ActorComponent>(args.User, out var actor) ||
!_toolSystem.HasQuality(args.Used, ScrewingQuality)) // Screwing because Pulsing already used by device linking
return;
_quickDialog.OpenDialog(actor.PlayerSession,
Loc.GetString("fax-machine-dialog-rename"),
Loc.GetString("fax-machine-dialog-field-name"),
(string newName) =>
{
if (component.FaxName == newName)
return;
if (newName.Length > 20)
{
_popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-long"), uid);
return;
}
if (component.KnownFaxes.ContainsValue(newName) && !_emag.CheckFlag(uid, EmagType.Interaction)) // Allow existing names if emagged for fun
{
_popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-exist"), uid);
return;
}
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.User):user} renamed {ToPrettyString(uid):tool} from \"{component.FaxName}\" to \"{newName}\"");
component.FaxName = newName;
_popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-set"), uid);
UpdateUserInterface(uid, component);
});
args.Handled = true;
}
private void OnEmagged(EntityUid uid, FaxMachineComponent component, ref GotEmaggedEvent args)
{
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
return;
if (_emag.CheckFlag(uid, EmagType.Interaction))
return;
args.Handled = true;
}
private void OnPacketReceived(EntityUid uid, FaxMachineComponent component, DeviceNetworkPacketEvent args)
{
if (!HasComp<DeviceNetworkComponent>(uid) || string.IsNullOrEmpty(args.SenderAddress))
return;
if (args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
{
switch (command)
{
case FaxConstants.FaxPingCommand:
var isForSyndie = _emag.CheckFlag(uid, EmagType.Interaction) &&
args.Data.ContainsKey(FaxConstants.FaxSyndicateData);
if (!isForSyndie && !component.ResponsePings)
return;
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, FaxConstants.FaxPongCommand },
{ FaxConstants.FaxNameData, component.FaxName }
};
_deviceNetworkSystem.QueuePacket(uid, args.SenderAddress, payload);
break;
case FaxConstants.FaxPongCommand:
if (!args.Data.TryGetValue(FaxConstants.FaxNameData, out string? faxName))
return;
component.KnownFaxes[args.SenderAddress] = faxName;
UpdateUserInterface(uid, component);
break;
case FaxConstants.FaxPrintCommand:
if (!args.Data.TryGetValue(FaxConstants.FaxPaperNameData, out string? name) ||
!args.Data.TryGetValue(FaxConstants.FaxPaperContentData, out string? content))
return;
args.Data.TryGetValue(FaxConstants.FaxPaperLabelData, out string? label);
args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState);
args.Data.TryGetValue(FaxConstants.FaxPaperStampedByData, out List<StampDisplayInfo>? stampedBy);
args.Data.TryGetValue(FaxConstants.FaxPaperPrototypeData, out string? prototypeId);
args.Data.TryGetValue(FaxConstants.FaxPaperLockedData, out bool? locked);
var printout = new FaxPrintout(content, name, label, prototypeId, stampState, stampedBy, locked ?? false);
Receive(uid, printout, args.SenderAddress);
break;
}
}
}
private void OnToggleInterface(EntityUid uid, FaxMachineComponent component, AfterActivatableUIOpenEvent args)
{
UpdateUserInterface(uid, component);
}
private void OnFileButtonPressed(EntityUid uid, FaxMachineComponent component, FaxFileMessage args)
{
args.Label = args.Label?[..Math.Min(args.Label.Length, FaxFileMessageValidation.MaxLabelSize)];
args.Content = args.Content[..Math.Min(args.Content.Length, FaxFileMessageValidation.MaxContentSize)];
PrintFile(uid, component, args);
}
private void OnCopyButtonPressed(EntityUid uid, FaxMachineComponent component, FaxCopyMessage args)
{
if (HasComp<MobStateComponent>(component.PaperSlot.Item))
_faxecute.Faxecute(uid, component); // when button pressed it will hurt the mob.
else
Copy(uid, component, args);
}
private void OnSendButtonPressed(EntityUid uid, FaxMachineComponent component, FaxSendMessage args)
{
if (HasComp<MobStateComponent>(component.PaperSlot.Item))
_faxecute.Faxecute(uid, component); // when button pressed it will hurt the mob.
else
Send(uid, component, args);
}
private void OnRefreshButtonPressed(EntityUid uid, FaxMachineComponent component, FaxRefreshMessage args)
{
Refresh(uid, component);
}
private void OnDestinationSelected(EntityUid uid, FaxMachineComponent component, FaxDestinationMessage args)
{
SetDestination(uid, args.Address, component);
}
private void UpdateAppearance(EntityUid uid, FaxMachineComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (TryComp<FaxableObjectComponent>(component.PaperSlot.Item, out var faxable))
component.InsertingState = faxable.InsertingState;
if (component.InsertingTimeRemaining > 0)
{
_appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Inserting);
Dirty(uid, component);
}
else if (component.PrintingTimeRemaining > 0)
_appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Printing);
else
_appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Normal);
}
private void UpdateUserInterface(EntityUid uid, FaxMachineComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var isPaperInserted = component.PaperSlot.Item != null;
var canSend = isPaperInserted &&
component.DestinationFaxAddress != null &&
component.SendTimeoutRemaining <= 0 &&
component.InsertingTimeRemaining <= 0;
var canCopy = isPaperInserted &&
component.SendTimeoutRemaining <= 0 &&
component.InsertingTimeRemaining <= 0;
var state = new FaxUiState(component.FaxName, component.KnownFaxes, canSend, canCopy, isPaperInserted, component.DestinationFaxAddress);
_userInterface.SetUiState(uid, FaxUiKey.Key, state);
}
/// <summary>
/// Set fax destination address not checking if he knows it exists
/// </summary>
public void SetDestination(EntityUid uid, string destAddress, FaxMachineComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
component.DestinationFaxAddress = destAddress;
UpdateUserInterface(uid, component);
}
/// <summary>
/// Clears current known fax info and make network scan ping
/// Adds special data to payload if it was emagged to identify itself as a Syndicate
/// </summary>
public void Refresh(EntityUid uid, FaxMachineComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
component.DestinationFaxAddress = null;
component.KnownFaxes.Clear();
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, FaxConstants.FaxPingCommand }
};
if (_emag.CheckFlag(uid, EmagType.Interaction))
payload.Add(FaxConstants.FaxSyndicateData, true);
_deviceNetworkSystem.QueuePacket(uid, null, payload);
}
/// <summary>
/// Makes fax print from a file from the computer. A timeout is set after copying,
/// which is shared by the send button.
/// </summary>
public void PrintFile(EntityUid uid, FaxMachineComponent component, FaxFileMessage args)
{
var prototype = args.OfficePaper ? component.PrintOfficePaperId : component.PrintPaperId;
var name = Loc.GetString("fax-machine-printed-paper-name");
var printout = new FaxPrintout(args.Content, name, args.Label, prototype);
component.PrintingQueue.Enqueue(printout);
component.SendTimeoutRemaining += component.SendTimeout;
UpdateUserInterface(uid, component);
// Unfortunately, since a paper entity does not yet exist, we have to emulate what LabelSystem will do.
var nameWithLabel = (args.Label is { } label) ? $"{name} ({label})" : name;
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):actor} " +
$"added print job to \"{component.FaxName}\" {ToPrettyString(uid):tool} " +
$"of {nameWithLabel}: {args.Content}");
}
/// <summary>
/// Copies the paper in the fax. A timeout is set after copying,
/// which is shared by the send button.
/// </summary>
public void Copy(EntityUid uid, FaxMachineComponent? component, FaxCopyMessage args)
{
if (!Resolve(uid, ref component))
return;
if (component.SendTimeoutRemaining > 0)
return;
var sendEntity = component.PaperSlot.Item;
if (sendEntity == null)
return;
if (!TryComp(sendEntity, out MetaDataComponent? metadata) ||
!TryComp<PaperComponent>(sendEntity, out var paper))
return;
TryComp<LabelComponent>(sendEntity, out var labelComponent);
TryComp<NameModifierComponent>(sendEntity, out var nameMod);
// TODO: See comment in 'Send()' about not being able to copy whole entities
var printout = new FaxPrintout(paper.Content,
nameMod?.BaseName ?? metadata.EntityName,
labelComponent?.CurrentLabel,
metadata.EntityPrototype?.ID ?? component.PrintPaperId,
paper.StampState,
paper.StampedBy,
paper.EditingDisabled);
component.PrintingQueue.Enqueue(printout);
component.SendTimeoutRemaining += component.SendTimeout;
// Don't play component.SendSound - it clashes with the printing sound, which
// will start immediately.
UpdateUserInterface(uid, component);
if (!args.Actor.IsValid()) // Goobstation - no log for automation
return;
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):actor} " +
$"added copy job to \"{component.FaxName}\" {ToPrettyString(uid):tool} " +
$"of {ToPrettyString(sendEntity):subject}: {printout.Content}");
}
/// <summary>
/// Sends message to addressee if paper is set and a known fax is selected
/// A timeout is set after sending, which is shared by the copy button.
/// </summary>
public void Send(EntityUid uid, FaxMachineComponent? component, FaxSendMessage args)
{
if (!Resolve(uid, ref component))
return;
if (component.SendTimeoutRemaining > 0)
return;
var sendEntity = component.PaperSlot.Item;
if (sendEntity == null)
return;
if (component.DestinationFaxAddress == null)
return;
if (!component.KnownFaxes.TryGetValue(component.DestinationFaxAddress, out var faxName))
return;
if (!TryComp(sendEntity, out MetaDataComponent? metadata) ||
!TryComp<PaperComponent>(sendEntity, out var paper))
return;
TryComp<NameModifierComponent>(sendEntity, out var nameMod);
TryComp<LabelComponent>(sendEntity, out var labelComponent);
var payload = new NetworkPayload()
{
{ DeviceNetworkConstants.Command, FaxConstants.FaxPrintCommand },
{ FaxConstants.FaxPaperNameData, nameMod?.BaseName ?? metadata.EntityName },
{ FaxConstants.FaxPaperLabelData, labelComponent?.CurrentLabel },
{ FaxConstants.FaxPaperContentData, paper.Content },
{ FaxConstants.FaxPaperLockedData, paper.EditingDisabled },
};
if (metadata.EntityPrototype != null)
{
// TODO: Ideally, we could just make a copy of the whole entity when it's
// faxed, in order to preserve visuals, etc.. This functionality isn't
// available yet, so we'll pass along the originating prototypeId and fall
// back to component.PrintPaperId in SpawnPaperFromQueue if we can't find one here.
payload[FaxConstants.FaxPaperPrototypeData] = metadata.EntityPrototype.ID;
}
if (paper.StampState != null)
{
payload[FaxConstants.FaxPaperStampStateData] = paper.StampState;
payload[FaxConstants.FaxPaperStampedByData] = paper.StampedBy;
}
_deviceNetworkSystem.QueuePacket(uid, component.DestinationFaxAddress, payload);
if (!args.Actor.IsValid()) // Goobstation - no log for automation
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):actor} " +
$"sent fax from \"{component.FaxName}\" {ToPrettyString(uid):tool} " +
$"to \"{faxName}\" ({component.DestinationFaxAddress}) " +
$"of {ToPrettyString(sendEntity):subject}: {paper.Content}");
component.SendTimeoutRemaining += component.SendTimeout;
_audioSystem.PlayPvs(component.SendSound, uid);
UpdateUserInterface(uid, component);
}
/// <summary>
/// Accepts a new message and adds it to the queue to print
/// If has parameter "notifyAdmins" also output a special message to admin chat.
/// </summary>
public void Receive(EntityUid uid, FaxPrintout printout, string? fromAddress = null, FaxMachineComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var faxName = Loc.GetString("fax-machine-popup-source-unknown");
if (fromAddress != null && component.KnownFaxes.TryGetValue(fromAddress, out var fax)) // If message received from unknown fax address
faxName = fax;
_popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-received", ("from", faxName)), uid);
_appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Printing);
if (component.NotifyAdmins)
NotifyAdmins(faxName);
component.PrintingQueue.Enqueue(printout);
}
private void SpawnPaperFromQueue(EntityUid uid, FaxMachineComponent? component = null)
{
if (!Resolve(uid, ref component) || component.PrintingQueue.Count == 0)
return;
var printout = component.PrintingQueue.Dequeue();
var entityToSpawn = printout.PrototypeId.Length == 0 ? component.PrintPaperId.ToString() : printout.PrototypeId;
var printed = Spawn(entityToSpawn, Transform(uid).Coordinates);
if (TryComp<PaperComponent>(printed, out var paper))
{
_paperSystem.SetContent((printed, paper), printout.Content);
// Apply stamps
if (printout.StampState != null)
{
foreach (var stamp in printout.StampedBy)
{
_paperSystem.TryStamp((printed, paper), stamp, printout.StampState);
}
}
paper.EditingDisabled = printout.Locked;
}
_metaData.SetEntityName(printed, printout.Name);
if (printout.Label is { } label)
{
_labelSystem.Label(printed, label);
}
_adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid):tool} printed {ToPrettyString(printed):subject}: {printout.Content}");
}
private void NotifyAdmins(string faxName)
{
_chat.SendAdminAnnouncement(Loc.GetString("fax-machine-chat-notify", ("fax", faxName)));
_audioSystem.PlayGlobal("/Audio/Machines/high_tech_confirm.ogg", Filter.Empty().AddPlayers(_adminManager.ActiveAdmins), false, AudioParams.Default.WithVolume(-8f));
}
}