using System.Linq; using Content.Shared.Administration.Logs; using Content.Shared.UserInterface; using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Tag; using Robust.Shared.Player; using Robust.Shared.Audio.Systems; using static Content.Shared.Paper.PaperComponent; namespace Content.Shared.Paper; public sealed class PaperSystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(BeforeUIOpen); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnInputTextMessage); SubscribeLocalEvent(OnPaperWrite); } private void OnMapInit(Entity entity, ref MapInitEvent args) { if (!string.IsNullOrEmpty(entity.Comp.Content)) { SetContent(entity, Loc.GetString(entity.Comp.Content)); } } private void OnInit(Entity entity, ref ComponentInit args) { entity.Comp.Mode = PaperAction.Read; UpdateUserInterface(entity); if (TryComp(entity, out var appearance)) { if (entity.Comp.Content != "") _appearance.SetData(entity, PaperVisuals.Status, PaperStatus.Written, appearance); if (entity.Comp.StampState != null) _appearance.SetData(entity, PaperVisuals.Stamp, entity.Comp.StampState, appearance); } } private void BeforeUIOpen(Entity entity, ref BeforeActivatableUIOpenEvent args) { entity.Comp.Mode = PaperAction.Read; UpdateUserInterface(entity); } private void OnExamined(Entity entity, ref ExaminedEvent args) { if (!args.IsInDetailsRange) return; using (args.PushGroup(nameof(PaperComponent))) { if (entity.Comp.Content != "") { args.PushMarkup( Loc.GetString( "paper-component-examine-detail-has-words", ("paper", entity) ) ); } if (entity.Comp.StampedBy.Count > 0) { var commaSeparated = string.Join(", ", entity.Comp.StampedBy.Select(s => Loc.GetString(s.StampedName))); args.PushMarkup( Loc.GetString( "paper-component-examine-detail-stamped-by", ("paper", entity), ("stamps", commaSeparated)) ); } } } private void OnInteractUsing(Entity entity, ref InteractUsingEvent args) { // only allow editing if there are no stamps or when using a cyberpen var editable = entity.Comp.StampedBy.Count == 0 || _tagSystem.HasTag(args.Used, "WriteIgnoreStamps"); if (_tagSystem.HasTag(args.Used, "Write")) { if (editable) { if (entity.Comp.EditingDisabled) { var paperEditingDisabledMessage = Loc.GetString("paper-tamper-proof-modified-message"); _popupSystem.PopupEntity(paperEditingDisabledMessage, entity, args.User); args.Handled = true; return; } var ev = new PaperWriteAttemptEvent(entity.Owner); RaiseLocalEvent(args.User, ref ev); if (ev.Cancelled) { if (ev.FailReason is not null) { var fileWriteMessage = Loc.GetString(ev.FailReason); _popupSystem.PopupClient(fileWriteMessage, entity.Owner, args.User); } args.Handled = true; return; } var writeEvent = new PaperWriteEvent(args.User, entity); RaiseLocalEvent(args.Used, ref writeEvent); entity.Comp.Mode = PaperAction.Write; _uiSystem.OpenUi(entity.Owner, PaperUiKey.Key, args.User); UpdateUserInterface(entity); } args.Handled = true; return; } // If a stamp, attempt to stamp paper if (TryComp(args.Used, out var stampComp) && TryStamp(entity, GetStampInfo(stampComp), stampComp.StampState)) { // successfully stamped, play popup var stampPaperOtherMessage = Loc.GetString("paper-component-action-stamp-paper-other", ("user", args.User), ("target", args.Target), ("stamp", args.Used)); _popupSystem.PopupEntity(stampPaperOtherMessage, args.User, Filter.PvsExcept(args.User, entityManager: EntityManager), true); var stampPaperSelfMessage = Loc.GetString("paper-component-action-stamp-paper-self", ("target", args.Target), ("stamp", args.Used)); _popupSystem.PopupClient(stampPaperSelfMessage, args.User, args.User); _audio.PlayPredicted(stampComp.Sound, entity, args.User); UpdateUserInterface(entity); } } private static StampDisplayInfo GetStampInfo(StampComponent stamp) { return new StampDisplayInfo { StampedName = stamp.StampedName, StampedColor = stamp.StampedColor }; } private void OnInputTextMessage(Entity entity, ref PaperInputTextMessage args) { var ev = new PaperWriteAttemptEvent(entity.Owner); RaiseLocalEvent(args.Actor, ref ev); if (ev.Cancelled) return; if (args.Text.Length <= entity.Comp.ContentSize) { SetContent(entity, args.Text); if (TryComp(entity, out var appearance)) _appearance.SetData(entity, PaperVisuals.Status, PaperStatus.Written, appearance); if (TryComp(entity, out MetaDataComponent? meta)) _metaSystem.SetEntityDescription(entity, "", meta); _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor):player} has written on {ToPrettyString(entity):entity} the following text: {args.Text}"); _audio.PlayPvs(entity.Comp.Sound, entity); } entity.Comp.Mode = PaperAction.Read; UpdateUserInterface(entity); } private void OnPaperWrite(Entity entity, ref PaperWriteEvent args) { _interaction.UseInHandInteraction(args.User, entity); } /// /// Accepts the name and state to be stamped onto the paper, returns true if successful. /// public bool TryStamp(Entity entity, StampDisplayInfo stampInfo, string spriteStampState) { if (!entity.Comp.StampedBy.Contains(stampInfo)) { entity.Comp.StampedBy.Add(stampInfo); Dirty(entity); if (entity.Comp.StampState == null && TryComp(entity, out var appearance)) { entity.Comp.StampState = spriteStampState; // Would be nice to be able to display multiple sprites on the paper // but most of the existing images overlap _appearance.SetData(entity, PaperVisuals.Stamp, entity.Comp.StampState, appearance); } } return true; } public void SetContent(Entity entity, string content) { entity.Comp.Content = content; Dirty(entity); UpdateUserInterface(entity); if (!TryComp(entity, out var appearance)) return; var status = string.IsNullOrWhiteSpace(content) ? PaperStatus.Blank : PaperStatus.Written; _appearance.SetData(entity, PaperVisuals.Status, status, appearance); } private void UpdateUserInterface(Entity entity) { _uiSystem.SetUiState(entity.Owner, PaperUiKey.Key, new PaperBoundUserInterfaceState(entity.Comp.Content, entity.Comp.StampedBy, entity.Comp.Mode)); } } /// /// Event fired when using a pen on paper, opening the UI. /// [ByRefEvent] public record struct PaperWriteEvent(EntityUid User, EntityUid Paper); /// /// Cancellable event for attempting to write on a piece of paper. /// /// The paper that the writing will take place on. [ByRefEvent] public record struct PaperWriteAttemptEvent(EntityUid Paper, string? FailReason = null, bool Cancelled = false);