diff --git a/Content.Client/StationRecords/GeneralStationRecordConsoleBoundUserInterface.cs b/Content.Client/StationRecords/GeneralStationRecordConsoleBoundUserInterface.cs
index e7bab71e38..a506a49177 100644
--- a/Content.Client/StationRecords/GeneralStationRecordConsoleBoundUserInterface.cs
+++ b/Content.Client/StationRecords/GeneralStationRecordConsoleBoundUserInterface.cs
@@ -1,3 +1,4 @@
+using Content.Shared._Sunrise.StationRecords;
using Content.Shared.StationRecords;
using Robust.Client.UserInterface;
@@ -22,6 +23,11 @@ public sealed class GeneralStationRecordConsoleBoundUserInterface : BoundUserInt
_window.OnFiltersChanged += (type, filterValue) =>
SendMessage(new SetStationRecordFilter(type, filterValue));
_window.OnDeleted += id => SendMessage(new DeleteStationRecord(id));
+
+ // Sunrise added start
+ _window.OnSaved += (record, id) => SendMessage(new SaveStationRecord(record, id));
+ _window.OnPrinted += id => SendMessage(new PrintStationRecord(id));
+ // Sunrise added end
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml b/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml
index 3615eb8e00..d5201a0781 100644
--- a/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml
+++ b/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml
@@ -17,9 +17,9 @@
-
+
-
+
diff --git a/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs b/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs
index 272e6c3b25..616aecf1e9 100644
--- a/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs
+++ b/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs
@@ -1,7 +1,12 @@
+using Content.Client._Sunrise.StationRecords;
+using Content.Client.Lobby;
+using Content.Client.Roles;
using Content.Shared.StationRecords;
using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
namespace Content.Client.StationRecords;
@@ -17,10 +22,30 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
private StationRecordFilterType _currentFilterType;
+ // Sunrise added start
+ public Action? OnSaved;
+ public Action? OnPrinted;
+
+ private readonly IEntityManager _entity;
+ private readonly IPrototypeManager _prototype;
+ private readonly ILocalizationManager _loc;
+ private readonly JobSystem _job;
+ private readonly LobbyUIController _controller;
+ // Sunrise added end
+
public GeneralStationRecordConsoleWindow()
{
RobustXamlLoader.Load(this);
+ // Sunrise added start
+ _entity = IoCManager.Resolve();
+ _prototype = IoCManager.Resolve();
+ _loc = IoCManager.Resolve();
+ var interfaceManager = IoCManager.Resolve();
+ _job = _entity.System();
+ _controller = interfaceManager.GetUIController();
+ // Sunrise added end
+
_currentFilterType = StationRecordFilterType.Name;
foreach (var item in Enum.GetValues())
@@ -111,7 +136,8 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
RecordContainerStatus.Text = state.SelectedKey == null
? Loc.GetString("general-station-record-console-no-record-found")
: Loc.GetString("general-station-record-console-select-record-info");
- PopulateRecordContainer(state.Record, state.CanDeleteEntries, state.SelectedKey);
+ // Sunrise edit
+ PopulateRecordContainer(state.Record, state.CanDeleteEntries, state.CanRedactSensitiveData, state.HasAccess, state.SelectedKey);
}
else
{
@@ -136,11 +162,17 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
RecordListing.SortItemsByText();
}
- private void PopulateRecordContainer(GeneralStationRecord record, bool enableDelete, uint? id)
+ // Sunrise edit
+ private void PopulateRecordContainer(GeneralStationRecord record, bool enableDelete, bool canRedactSensitiveData, bool hasAccess, uint? id)
{
RecordContainer.RemoveAllChildren();
- var newRecord = new GeneralRecord(record, enableDelete, id);
+ // Sunrise edit start
+ var newRecord =
+ new SunriseGeneralRecord(record, enableDelete, canRedactSensitiveData, hasAccess, id, in _entity, in _prototype, in _loc, in _job, in _controller);
+ // Sunrise edit end
newRecord.OnDeletePressed = OnDeleted;
+ newRecord.OnPrintPressed = OnPrinted;
+ newRecord.OnSaveButtonPressed = OnSaved;
RecordContainer.AddChild(newRecord);
}
diff --git a/Content.Client/_Sunrise/StationRecords/SunriseGeneralRecord.xaml b/Content.Client/_Sunrise/StationRecords/SunriseGeneralRecord.xaml
new file mode 100644
index 0000000000..13f6698e10
--- /dev/null
+++ b/Content.Client/_Sunrise/StationRecords/SunriseGeneralRecord.xaml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_Sunrise/StationRecords/SunriseGeneralRecord.xaml.cs b/Content.Client/_Sunrise/StationRecords/SunriseGeneralRecord.xaml.cs
new file mode 100644
index 0000000000..22cc156b45
--- /dev/null
+++ b/Content.Client/_Sunrise/StationRecords/SunriseGeneralRecord.xaml.cs
@@ -0,0 +1,282 @@
+using System.Linq;
+using Content.Client.Lobby;
+using Content.Client.Roles;
+using Content.Shared._Sunrise.Helpers;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
+using Content.Shared.StationRecords;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Enums;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client._Sunrise.StationRecords;
+
+[GenerateTypedNameReferences]
+public sealed partial class SunriseGeneralRecord : BoxContainer
+{
+ private readonly IEntityManager _entity;
+ private readonly IPrototypeManager _prototype;
+ private readonly ILocalizationManager _loc;
+ private readonly JobSystem _job;
+ private readonly LobbyUIController _controller;
+
+ public Action? OnSaveButtonPressed;
+ public Action? OnPrintPressed;
+ public Action? OnDeletePressed;
+
+ private EntityUid _previewDummy;
+ private readonly HumanoidCharacterProfile? _profile;
+
+ private readonly List _allSpecies;
+ private readonly List _allJobs;
+ private readonly Gender[] _allGender;
+
+ private readonly bool _hasAccess;
+
+ // Копия этого хранится в серверной системе
+ private const int MaxAgeLength = 6;
+
+ public SunriseGeneralRecord(GeneralStationRecord record,
+ bool canDelete,
+ bool canRedactSensitiveData,
+ bool hasAccess,
+ uint? id,
+ in IEntityManager entity,
+ in IPrototypeManager prototype,
+ in ILocalizationManager loc,
+ in JobSystem job,
+ in LobbyUIController controller)
+ {
+ RobustXamlLoader.Load(this);
+
+ _entity = entity;
+ _prototype = prototype;
+ _loc = loc;
+ _job = job;
+ _controller = controller;
+
+ _profile = record.HumanoidProfile;
+ _allSpecies = _prototype.EnumeratePrototypes().ToList();
+ _allJobs = _prototype.EnumeratePrototypes().ToList();
+ _allGender = Enum.GetValues();
+
+ // Сортировка рас и работ по имени
+ _allSpecies.Sort((a, b)
+ => string.Compare(_loc.GetString(a.Name), _loc.GetString(b.Name), StringComparison.Ordinal));
+
+ _allJobs.Sort((a, b)
+ => string.Compare(a.LocalizedName, b.LocalizedName, StringComparison.Ordinal));
+
+ Fingerprint.Editable = canRedactSensitiveData && hasAccess;
+ Dna.Editable = canRedactSensitiveData && hasAccess;
+
+ if (canDelete && id != null )
+ {
+ DeleteButton.Visible = true;
+ DeleteButton.OnPressed += _ => OnDeletePressed?.Invoke(id.Value);
+ }
+
+ if (id != null )
+ {
+ SaveButton.Visible = true;
+ SaveButton.OnPressed += _ =>
+ {
+ var updatedRecord = BuildUpdatedRecord(record);
+ OnSaveButtonPressed?.Invoke(updatedRecord, id.Value);
+ };
+ }
+
+ if (id != null )
+ {
+ PrintButton.Visible = true;
+ PrintButton.OnPressed += _ => OnPrintPressed?.Invoke(id.Value);
+ }
+
+ _hasAccess = hasAccess;
+
+ UpdateEditableInfo(record);
+ UpdateHeading(record);
+ ReloadPreview(record.JobPrototype);
+
+ MakeDropDownSelectable();
+ CheckAccess();
+ CheckChanges();
+ }
+
+ protected override void ExitedTree()
+ {
+ base.ExitedTree();
+
+ _entity.DeleteEntity(_previewDummy);
+ }
+
+ ///
+ /// Обновляет информацию, которая может быть изменена игроком.
+ ///
+ private void UpdateEditableInfo(GeneralStationRecord record)
+ {
+ Name.Text = record.Name;
+ Age.Text = record.Age.ToString();
+
+ for (var i = 0; i < _allGender.Length; i++)
+ {
+ var item = _allGender[i];
+ var name = _loc.GetString("station-records-gender", ("gender", item.ToString()));
+
+ Gender.AddItem(name, i);
+
+ if (item == record.Gender)
+ Gender.SelectId(i);
+ }
+
+ for (var i = 0; i < _allSpecies.Count; i++)
+ {
+ var item = _allSpecies[i];
+
+ if (item.StationRecordsHidden && item.ID != record.Species)
+ continue;
+
+ var name = _loc.GetString(item.Name);
+ Species.AddItem(name, i);
+
+ if (item.ID == record.Species)
+ Species.SelectId(i);
+ }
+
+ for (var i = 0; i < _allJobs.Count; i++)
+ {
+ var item = _allJobs[i];
+
+ if (item.OverrideConsoleVisibility == false && item.ID != record.JobPrototype)
+ continue;
+
+ var name = item.LocalizedName;
+ Job.AddItem(name, i);
+
+ if (item.ID == record.JobPrototype)
+ Job.SelectId(i);
+ }
+
+ Fingerprint.Text = record.Fingerprint ?? _loc.GetString("generic-not-available-shorthand");
+ Dna.Text = record.DNA ?? _loc.GetString("generic-not-available-shorthand");
+
+ Personality.Placeholder = new Rope.Leaf(_loc.GetString("station-records-ui-personality-placeholder"));
+
+ if (!string.IsNullOrEmpty(record.Personality))
+ Personality.TextRope = new Rope.Leaf(record.Personality);
+ }
+
+ ///
+ /// Обновляет заголовок сверху персонажа.
+ ///
+ private void UpdateHeading(GeneralStationRecord record)
+ {
+ SubText.Visible = false;
+ NameHeading.Text = record.Name;
+
+ if (!_prototype.TryIndex(record.JobPrototype, out var job))
+ return;
+
+ SubText.Text = $"{job.LocalizedName}";
+ SubText.Visible = true;
+
+ if (!_job.TryGetDepartment(job.ID, out var department))
+ return;
+
+ SubText.Text = $"{_loc.GetString(department.Name)}, {job.LocalizedName}";
+ }
+
+ ///
+ /// Обновляет превью игрока.
+ ///
+ private void ReloadPreview(ProtoId jobProtoId)
+ {
+ if (!_prototype.TryIndex(jobProtoId, out var job))
+ return;
+
+ _entity.DeleteEntity(_previewDummy);
+ _previewDummy = EntityUid.Invalid;
+
+ if (job.JobEntity != null)
+ _previewDummy = _entity.Spawn(job.JobEntity, MapCoordinates.Nullspace);
+
+ if (_profile != null && _prototype.HasIndex(_profile.Species))
+ _previewDummy = _controller.LoadProfileEntity(_profile, job, true);
+
+ SpriteView.SetEntity(_previewDummy);
+ }
+
+ ///
+ /// Создает новую структуру, помещающую в себя из полей в интерфейсе для отправки на сервере для сохранения.
+ /// Перед этим проводит валидацию строк и проверяет, что данные не обосраны.
+ ///
+ private GeneralStationRecord BuildUpdatedRecord(GeneralStationRecord original)
+ {
+ var textAge = Age.Text.SanitizeInput(MaxAgeLength);
+
+ var updated = original with
+ {
+ Name = Name.Text,
+ Age = int.TryParse(textAge, out var ageVal) ? ageVal : original.Age,
+ Gender = _allGender[Gender.SelectedId],
+ Species = _allSpecies[Species.SelectedId].ID,
+ JobPrototype = _allJobs[Job.SelectedId].ID,
+ Fingerprint = Fingerprint.Text,
+ DNA = Dna.Text,
+ Personality = Rope.Collapse(Personality.TextRope),
+ };
+
+ return GeneralStationRecord.SanitizeRecord(updated, in _prototype);
+ }
+
+ private void MakeDropDownSelectable()
+ {
+ // Почему это не встроено в сами кнопки
+ foreach (var child in ControlGrid.Children)
+ {
+ if (child is not OptionButton optionButton)
+ continue;
+
+ optionButton.OnItemSelected += Select;
+ }
+ }
+
+ private void Select(OptionButton.ItemSelectedEventArgs args)
+ {
+ args.Button.SelectId(args.Id);
+ MakeSaveAvailable();
+ }
+
+ private void CheckAccess()
+ {
+ Name.Editable = _hasAccess;
+ Age.Editable = _hasAccess;
+ Personality.Editable = _hasAccess;
+ Gender.Disabled = !_hasAccess;
+ Species.Disabled = !_hasAccess;
+ Job.Disabled = !_hasAccess;
+ }
+
+ private void CheckChanges()
+ {
+ foreach (var child in ControlGrid.Children)
+ {
+ if (child is not LineEdit lineEdit)
+ continue;
+
+ lineEdit.OnTextChanged += _ => MakeSaveAvailable();
+ }
+
+ Personality.OnTextChanged += _ => MakeSaveAvailable();
+ }
+
+ private void MakeSaveAvailable()
+ {
+ SaveButton.Disabled = !_hasAccess;
+ }
+}
diff --git a/Content.Server/StationRecords/Components/GeneralStationRecordConsoleComponent.cs b/Content.Server/StationRecords/Components/GeneralStationRecordConsoleComponent.cs
index a6356f0baa..a3dd3dc7e2 100644
--- a/Content.Server/StationRecords/Components/GeneralStationRecordConsoleComponent.cs
+++ b/Content.Server/StationRecords/Components/GeneralStationRecordConsoleComponent.cs
@@ -1,5 +1,8 @@
using Content.Server.StationRecords.Systems;
+using Content.Shared.Radio;
using Content.Shared.StationRecords;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
namespace Content.Server.StationRecords.Components;
@@ -24,4 +27,38 @@ public sealed partial class GeneralStationRecordConsoleComponent : Component
///
[DataField]
public bool CanDeleteEntries;
+
+ // Sunrise added start - возможность редактировать отпечатки через емаг
+ [DataField]
+ public bool CanRedactSensitiveData;
+
+ [DataField]
+ public bool HasAccess;
+
+ [DataField]
+ public bool Silent;
+
+ [DataField]
+ public bool SkipAccessCheck;
+
+ [DataField]
+ public SoundSpecifier SuccessfulSound = new SoundPathSpecifier("/Audio/Effects/Cargo/ping.ogg");
+
+ [DataField]
+ public SoundSpecifier FailedSound = new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_sigh.ogg");
+
+ public TimeSpan NextPrintTime = TimeSpan.Zero;
+
+ [DataField]
+ public TimeSpan PrintCooldown = TimeSpan.FromSeconds(5);
+
+ [DataField]
+ public EntProtoId Paper = "Paper";
+
+ [DataField]
+ public SoundSpecifier SoundPrint = new SoundPathSpecifier("/Audio/Machines/short_print_and_rip.ogg");
+
+ [DataField]
+ public List> AnnouncementChannels = ["Command", "Security"];
+ // Sunrise added end
}
diff --git a/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs b/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs
index 87246ab675..6e061cb1c2 100644
--- a/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs
+++ b/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs
@@ -6,7 +6,7 @@ using Robust.Server.GameObjects;
namespace Content.Server.StationRecords.Systems;
-public sealed class GeneralStationRecordConsoleSystem : EntitySystem
+public sealed partial class GeneralStationRecordConsoleSystem : EntitySystem
{
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly StationSystem _station = default!;
@@ -20,11 +20,14 @@ public sealed class GeneralStationRecordConsoleSystem : EntitySystem
Subs.BuiEvents(GeneralStationRecordConsoleKey.Key, subs =>
{
- subs.Event(UpdateUserInterface);
+ subs.Event(OnOpened);
subs.Event(OnKeySelected);
subs.Event(OnFiltersChanged);
subs.Event(OnRecordDelete);
});
+
+ // Sunrise added
+ InitializeSunrise();
}
private void OnRecordDelete(Entity ent, ref DeleteStationRecord args)
@@ -34,6 +37,23 @@ public sealed class GeneralStationRecordConsoleSystem : EntitySystem
var owning = _station.GetOwningStation(ent.Owner);
+ // Sunrise added start
+ if (owning == null)
+ return;
+
+ // Дополнительная серверная проверка на случай педиков с читами
+ if (!HasAccess(ent, args.Actor))
+ return;
+
+ if (!_stationRecords.TryGetRecord(new StationRecordKey(args.Id, owning.Value), out var record))
+ return;
+
+ var message = Loc.GetString("station-record-deleted", ("name", record.Name));
+ var popup = Loc.GetString("station-record-deleted-successfully");
+
+ DoFeedback(ent, message, popup);
+ // Sunrise added end
+
if (owning != null)
_stationRecords.RemoveRecord(new StationRecordKey(args.Id, owning.Value));
UpdateUserInterface(ent); // Apparently an event does not get raised for this.
@@ -93,7 +113,8 @@ public sealed class GeneralStationRecordConsoleSystem : EntitySystem
var key = new StationRecordKey(id, owningStation.Value);
_stationRecords.TryGetRecord(key, out var record, stationRecords);
- GeneralStationRecordConsoleState newState = new(id, record, listing, console.Filter, ent.Comp.CanDeleteEntries);
+ // Sunrise edit
+ GeneralStationRecordConsoleState newState = new(id, record, listing, console.Filter, ent.Comp.CanDeleteEntries, ent.Comp.CanRedactSensitiveData, ent.Comp.HasAccess);
_ui.SetUiState(uid, GeneralStationRecordConsoleKey.Key, newState);
}
}
diff --git a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs
index a466ddbcf5..a5d72fdb9e 100644
--- a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs
+++ b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs
@@ -179,6 +179,7 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
Fingerprint = mobFingerprint,
DNA = dna,
Silicon = silicon, // Sunrise-Edit
+ HumanoidProfile = profile, // Sunrise edit
};
var key = AddRecordEntry(station, record);
diff --git a/Content.Server/_Sunrise/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs b/Content.Server/_Sunrise/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs
new file mode 100644
index 0000000000..266b1b3c3e
--- /dev/null
+++ b/Content.Server/_Sunrise/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs
@@ -0,0 +1,234 @@
+using Content.Server.Hands.Systems;
+using Content.Server.Popups;
+using Content.Server.Radio.EntitySystems;
+using Content.Server.Roles.Jobs;
+using Content.Server.StationRecords.Components;
+using Content.Shared._Sunrise.StationRecords;
+using Content.Shared.Access.Systems;
+using Content.Shared.Emag.Systems;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Paper;
+using Content.Shared.Roles;
+using Content.Shared.StationRecords;
+using Robust.Server.Audio;
+using Robust.Shared.Audio;
+using Robust.Shared.Enums;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Server.StationRecords.Systems;
+
+public sealed partial class GeneralStationRecordConsoleSystem
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly AudioSystem _audio = default!;
+ [Dependency] private readonly RadioSystem _radio = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly AccessReaderSystem _access = default!;
+ [Dependency] private readonly HandsSystem _hands = default!;
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly JobSystem _job = default!;
+ [Dependency] private readonly PaperSystem _paper = default!;
+
+ private void InitializeSunrise()
+ {
+ SubscribeLocalEvent(OnEmagged);
+
+ Subs.BuiEvents(GeneralStationRecordConsoleKey.Key, subs =>
+ {
+ subs.Event(OnSave);
+ subs.Event(Print);
+ });
+ }
+
+ private void OnSave(Entity ent, ref SaveStationRecord args)
+ {
+ var owning = _station.GetOwningStation(ent.Owner);
+
+ if (owning == null)
+ {
+ _audio.PlayPvs(ent.Comp.FailedSound, ent);
+ return;
+ }
+
+ // Дополнительная серверная проверка на случай педиков с читами
+ if (!HasAccess(ent, args.Actor))
+ {
+ _audio.PlayPvs(ent.Comp.FailedSound, ent);
+ return;
+ }
+
+ // Удаляем старую запись
+ if (!_stationRecords.RemoveRecord(new StationRecordKey(args.Id, owning.Value)))
+ {
+ _audio.PlayPvs(ent.Comp.FailedSound, ent);
+ return;
+ }
+
+ // Добавляем новую
+ var record = GeneralStationRecord.SanitizeRecord(args.Record, in _prototype);
+ var id = _stationRecords.AddRecordEntry(owning.Value, record);
+ ent.Comp.ActiveKey = id.Id;
+
+ var message = Loc.GetString("station-record-updated", ("name", args.Record.Name));
+ var popup = Loc.GetString("station-record-updated-successfully");
+
+ DoFeedback(ent, message, popup);
+
+ UpdateUserInterface(ent);
+ }
+
+ private void OnEmagged(Entity ent, ref GotEmaggedEvent args)
+ {
+ if (ent.Comp.CanRedactSensitiveData
+ && ent.Comp.CanDeleteEntries
+ && ent.Comp.Silent
+ && ent.Comp.SkipAccessCheck)
+ return;
+
+ if (args.Handled)
+ return;
+
+ ent.Comp.CanDeleteEntries = true;
+ ent.Comp.CanRedactSensitiveData = true;
+ ent.Comp.Silent = true;
+ ent.Comp.SkipAccessCheck = true;
+
+ UpdateUserInterface(ent);
+ args.Handled = true;
+ }
+
+ private void Print(Entity ent, ref PrintStationRecord args)
+ {
+ var user = args.Actor;
+
+ if (_timing.CurTime < ent.Comp.NextPrintTime)
+ {
+ _popup.PopupEntity(Loc.GetString("forensic-scanner-printer-not-ready"), ent, user);
+ _audio.PlayPvs(ent.Comp.FailedSound, ent);
+ return;
+ }
+
+ var owning = _station.GetOwningStation(ent.Owner);
+
+ if (owning == null)
+ {
+ _audio.PlayPvs(ent.Comp.FailedSound, ent);
+ return;
+ }
+
+ if (!_stationRecords.TryGetRecord(new StationRecordKey(args.Id, owning.Value), out var record))
+ {
+ _audio.PlayPvs(ent.Comp.FailedSound, ent);
+ return;
+ }
+
+ // Spawn a piece of paper.
+ var printed = Spawn(ent.Comp.Paper, Transform(ent).Coordinates);
+ _hands.PickupOrDrop(args.Actor, printed, checkActionBlocker: false);
+
+ if (!TryComp(printed, out var paperComp))
+ {
+ _audio.PlayPvs(ent.Comp.FailedSound, ent);
+ return;
+ }
+
+ var documentName = Loc.GetString("printed-station-records-document-name", ("name", record.Name));
+ _metaData.SetEntityName(printed, documentName);
+
+ var text = Loc.GetString(
+ "printed-station-records-content",
+ ("name", record.Name),
+ ("job", GetJobName(record.JobPrototype)),
+ ("department", GetDepartmentName(record.JobPrototype)),
+ ("age", record.Age),
+ ("gender", GetGenderName(record.Gender)),
+ ("species", GetSpeciesName(record.Species)),
+ ("dna", record.DNA ?? Loc.GetString("printed-station-records-unrecognized")),
+ ("fingerprint", record.Fingerprint ?? Loc.GetString("printed-station-records-unrecognized")),
+ ("personality", GetPersonality(record.Personality))
+ );
+
+ _paper.SetContent((printed, paperComp), text);
+ _audio.PlayPvs(ent.Comp.SoundPrint, ent,
+ AudioParams.Default
+ .WithVariation(0.25f)
+ .WithVolume(4f)
+ .WithRolloffFactor(2.8f)
+ .WithMaxDistance(4.5f));
+
+ ent.Comp.NextPrintTime = _timing.CurTime + ent.Comp.PrintCooldown;
+ }
+
+ private void DoFeedback(Entity ent, string message, string popup)
+ {
+ _popup.PopupEntity(popup, ent);
+
+ if (ent.Comp.Silent)
+ return;
+
+ foreach (var channel in ent.Comp.AnnouncementChannels)
+ {
+ _radio.SendRadioMessage(ent, message, channel, ent);
+ }
+
+ _audio.PlayPvs(ent.Comp.SuccessfulSound, ent);
+ }
+
+ private void OnOpened(Entity ent, ref BoundUIOpenedEvent msg)
+ {
+ ent.Comp.HasAccess = HasAccess(ent, msg.Actor);
+ UpdateUserInterface(ent);
+ }
+
+ ///
+ /// Проверяет наличие у персонажа доступа к консоли.
+ ///
+ private bool HasAccess(Entity ent, EntityUid actor)
+ {
+ var allowed = _access.IsAllowed(actor, ent);
+ return allowed || ent.Comp.SkipAccessCheck;
+ }
+
+ #region Helpers
+
+ private string GetJobName(ProtoId job)
+ {
+ if (!_prototype.TryIndex(job, out var jobPrototype))
+ return Loc.GetString("printed-station-records-unrecognized");
+
+ return jobPrototype.LocalizedName;
+ }
+
+ private string GetDepartmentName(ProtoId job)
+ {
+ if (!_job.TryGetDepartment(job, out var department))
+ return Loc.GetString("printed-station-records-unrecognized");
+
+ return Loc.GetString(department.Name);
+ }
+
+ private string GetGenderName(Gender gender)
+ {
+ return Loc.GetString("station-records-gender", ("gender", gender.ToString()));
+ }
+
+ private string GetSpeciesName(ProtoId species)
+ {
+ if (!_prototype.TryIndex(species, out var speciesPrototype))
+ return Loc.GetString("printed-station-records-unrecognized");
+
+ return Loc.GetString(speciesPrototype.Name);
+ }
+
+ private string GetPersonality(string personality)
+ {
+ if (string.IsNullOrEmpty(personality))
+ return Loc.GetString("printed-station-records-unrecognized");
+
+ return personality;
+ }
+
+ #endregion
+}
diff --git a/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs b/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs
index e93d66c892..47ffebaa9d 100644
--- a/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs
+++ b/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs
@@ -199,6 +199,9 @@ public sealed partial class SpeciesPrototype : IPrototype
///
[DataField]
public int StandardDensity = 120;
+
+ [DataField]
+ public bool StationRecordsHidden;
//Sunrise end
diff --git a/Content.Shared/StationRecords/GeneralRecordsUi.cs b/Content.Shared/StationRecords/GeneralRecordsUi.cs
index 2105c53df2..6e0c7b12c9 100644
--- a/Content.Shared/StationRecords/GeneralRecordsUi.cs
+++ b/Content.Shared/StationRecords/GeneralRecordsUi.cs
@@ -38,21 +38,28 @@ public sealed class GeneralStationRecordConsoleState : BoundUserInterfaceState
public readonly Dictionary? RecordListing;
public readonly StationRecordsFilter? Filter;
public readonly bool CanDeleteEntries;
+ public readonly bool CanRedactSensitiveData; // Sunrise added
+ public readonly bool HasAccess; // Sunrise added
public GeneralStationRecordConsoleState(uint? key,
GeneralStationRecord? record,
Dictionary? recordListing,
StationRecordsFilter? newFilter,
- bool canDeleteEntries)
+ bool canDeleteEntries,
+ bool canRedactSensitiveData, // Sunrise added
+ bool hasAccess) // Sunrise added
{
SelectedKey = key;
Record = record;
RecordListing = recordListing;
Filter = newFilter;
CanDeleteEntries = canDeleteEntries;
+ CanRedactSensitiveData = canRedactSensitiveData; // Sunrise added
+ HasAccess = hasAccess; // Sunrise added
}
- public GeneralStationRecordConsoleState() : this(null, null, null, null, false)
+ // Sunrise edit
+ public GeneralStationRecordConsoleState() : this(null, null, null, null, false, false, false)
{
}
diff --git a/Content.Shared/StationRecords/GeneralStationRecord.cs b/Content.Shared/StationRecords/GeneralStationRecord.cs
index 9e7bac423e..76d4cb8fe8 100644
--- a/Content.Shared/StationRecords/GeneralStationRecord.cs
+++ b/Content.Shared/StationRecords/GeneralStationRecord.cs
@@ -1,4 +1,9 @@
+using Content.Shared._Sunrise.Helpers;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
using Robust.Shared.Enums;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.StationRecords;
@@ -69,6 +74,43 @@ public sealed record GeneralStationRecord
[DataField]
public string? DNA;
+ // Sunrise added start
[DataField]
- public bool Silicon; // Sunrise-Edit
+ public bool Silicon;
+
+ [DataField]
+ public HumanoidCharacterProfile? HumanoidProfile;
+
+ [DataField]
+ public string Personality = string.Empty;
+
+ [NonSerialized] private const int MaxNameLength = 64;
+ [NonSerialized] private const int MaxAge = 10000;
+ [NonSerialized] private const int MaxFingerprintLength = 32;
+ [NonSerialized] private const int MaxDnaLength = 16;
+ [NonSerialized] private const int MaxPersonalityLength = 1024;
+
+ [NonSerialized] private static readonly ProtoId FallbackJobPrototype = "Passenger";
+ [NonSerialized] private static readonly ProtoId FallbackSpeciesPrototype = "Human";
+
+ ///
+ /// Санитизирует данные, требуется для механики изменения и сохранения данных в консоли станционного учета.
+ ///
+ public static GeneralStationRecord SanitizeRecord(GeneralStationRecord original, in IPrototypeManager prototype)
+ {
+ var updated = original with
+ {
+ Name = original.Name.SanitizeInput(MaxNameLength),
+ Age = original.Age <= MaxAge ? original.Age : MaxAge,
+ Species = prototype.TryIndex(original.Species, out var species) ? species.ID : FallbackSpeciesPrototype,
+ JobPrototype = prototype.TryIndex(original.JobPrototype, out var job) ? job.ID : FallbackJobPrototype,
+ Fingerprint = original.Fingerprint.SanitizeInput(MaxFingerprintLength),
+ DNA = original.DNA.SanitizeInput(MaxDnaLength),
+ Personality = original.Personality.SanitizeInput(MaxPersonalityLength),
+ };
+
+ return updated;
+ }
+
+ // Sunrise added end
}
diff --git a/Content.Shared/_Sunrise/Helpers/StringExtensions.cs b/Content.Shared/_Sunrise/Helpers/StringExtensions.cs
new file mode 100644
index 0000000000..29571806bb
--- /dev/null
+++ b/Content.Shared/_Sunrise/Helpers/StringExtensions.cs
@@ -0,0 +1,39 @@
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Content.Shared._Sunrise.Helpers;
+
+public static class StringExtensions
+{
+ private static readonly Regex AllowedCharsRegex = new Regex(
+ @"[^a-zA-Zа-яА-ЯёЁ0-9\s.,!?;:\-_\(\)\[\]{}""'/\\@#%\^&\*\+=<>]",
+ RegexOptions.Compiled | RegexOptions.CultureInvariant
+ );
+
+ ///
+ /// Санитизация пользовательского ввода:
+ /// - Убирает лишние пробелы по краям
+ /// - Обрезает до maxLength (если задан)
+ /// - Убирает недопустимые символы
+ /// - Нормализует Unicode
+ ///
+ ///
+ /// Рекомендуется для UI, содержащих LineEdit или подобные возможности передать текст на сервер.
+ ///
+ public static string SanitizeInput(this string? input, int? maxLength = null)
+ {
+ if (string.IsNullOrEmpty(input))
+ return string.Empty;
+
+ input = input.Trim();
+
+ if (maxLength.HasValue && input.Length > maxLength.Value)
+ input = input.Substring(0, maxLength.Value);
+
+ input = input.Normalize(NormalizationForm.FormC);
+
+ input = AllowedCharsRegex.Replace(input, string.Empty);
+
+ return input;
+ }
+}
diff --git a/Content.Shared/_Sunrise/StationRecords/SunriseStationRecordEvents.cs b/Content.Shared/_Sunrise/StationRecords/SunriseStationRecordEvents.cs
new file mode 100644
index 0000000000..f11a10b445
--- /dev/null
+++ b/Content.Shared/_Sunrise/StationRecords/SunriseStationRecordEvents.cs
@@ -0,0 +1,17 @@
+using Content.Shared.StationRecords;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._Sunrise.StationRecords;
+
+[Serializable, NetSerializable]
+public sealed class SaveStationRecord(GeneralStationRecord record, uint id) : BoundUserInterfaceMessage
+{
+ public readonly uint Id = id;
+ public readonly GeneralStationRecord Record = record;
+}
+
+[Serializable, NetSerializable]
+public sealed class PrintStationRecord(uint id) : BoundUserInterfaceMessage
+{
+ public readonly uint Id = id;
+}
diff --git a/Resources/Locale/ru-RU/_strings/_sunrise/station-records/printable.ftl b/Resources/Locale/ru-RU/_strings/_sunrise/station-records/printable.ftl
new file mode 100644
index 0000000000..2fcbd86bf8
--- /dev/null
+++ b/Resources/Locale/ru-RU/_strings/_sunrise/station-records/printable.ftl
@@ -0,0 +1,27 @@
+printed-station-records-content =
+
+
+ [head=1]NanoTransen[/head]
+ [bold]Распечатка из базы данных экипажа станции[/bold]
+ ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
+
+ [head=2]Основная информация[/head]
+
+ [bullet] [color=#594D4A][bold]Имя:[/bold][/color] [italic]{$name}[/italic]
+ [bullet] [color=#594D4A][bold]Должность:[/bold][/color] [italic]{$job}[/italic]
+ [bullet] [color=#594D4A][bold]Отдел:[/bold][/color] [italic]{$department}[/italic]
+ [bullet] [color=#594D4A][bold]Возраст:[/bold][/color] [italic]{$age}[/italic]
+ [bullet] [color=#594D4A][bold]Пол:[/bold][/color] [italic]{$gender}[/italic]
+ [bullet] [color=#594D4A][bold]Раса:[/bold][/color] [italic]{$species}[/italic]
+
+ [head=2]Биометрические данные[/head]
+
+ [bullet] [color=#5BA4CF][bold]ДНК:[/bold][/color] [mono]{$dna}[/mono]
+ [bullet] [color=#5BA4CF][bold]Отпечаток пальцев:[/bold][/color] [mono]{$fingerprint}[/mono]
+
+ [head=2]Характеристика[/head]
+
+ [italic]{$personality}[/italic]
+
+printed-station-records-document-name = Распечатка на {$name}
+printed-station-records-unrecognized = Неустановлено
diff --git a/Resources/Locale/ru-RU/_strings/_sunrise/station-records/radio.ftl b/Resources/Locale/ru-RU/_strings/_sunrise/station-records/radio.ftl
new file mode 100644
index 0000000000..b60d0cee61
--- /dev/null
+++ b/Resources/Locale/ru-RU/_strings/_sunrise/station-records/radio.ftl
@@ -0,0 +1,2 @@
+station-record-updated = Запись в базе данных для «{$name}» была обновлена!
+station-record-deleted = [bold]Запись в базе данных для «{$name}» была УДАЛЕНА![/bold]
diff --git a/Resources/Locale/ru-RU/_strings/_sunrise/station-records/ui.ftl b/Resources/Locale/ru-RU/_strings/_sunrise/station-records/ui.ftl
new file mode 100644
index 0000000000..5c82a55d34
--- /dev/null
+++ b/Resources/Locale/ru-RU/_strings/_sunrise/station-records/ui.ftl
@@ -0,0 +1,20 @@
+station-records-gender =
+ { $gender ->
+ [Male] Мужской
+ [Female] Женский
+ [Epicene] Двуполый
+ *[Neuter] Бесполый
+ }
+
+station-records-ui-name = Имя:
+station-records-ui-age = Возраст:
+station-records-ui-job = Должность:
+station-records-ui-species = Раса:
+station-records-ui-gender = Пол:
+station-records-ui-fingerprint = Отпечатки:
+station-records-ui-dna = ДНК:
+station-records-ui-personality = Характеристика:
+station-records-ui-personality-placeholder = Здесь будет текст...
+
+station-records-ui-save = Сохранить
+station-records-ui-print = Распечатать
diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
index 78c3f56bc7..25fb085269 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
@@ -408,6 +408,10 @@
name: station records computer
description: This can be used to check station records.
components:
+ # Sunrise added start
+ - type: AccessReader
+ access: [["Security"], ["HeadOfPersonnel"]]
+ # Sunrise added end
- type: GeneralStationRecordConsole
- type: UserInterface
interfaces:
diff --git a/Resources/Prototypes/Species/terminator.yml b/Resources/Prototypes/Species/terminator.yml
index 22fe888e32..7169070592 100644
--- a/Resources/Prototypes/Species/terminator.yml
+++ b/Resources/Prototypes/Species/terminator.yml
@@ -12,6 +12,7 @@
dollPrototype: MobSkeletonPersonDummy
skinColoration: TintedHues
buttScanTexture: /Textures/_Sunrise/ButtsScans/human.png # Sunrise-Edit
+ stationRecordsHidden: true
- type: bodyType
id: TerminatorNormal
diff --git a/Resources/Prototypes/_Sunrise/Species/abductor.yml b/Resources/Prototypes/_Sunrise/Species/abductor.yml
index 4f752be846..e6a4d631af 100644
--- a/Resources/Prototypes/_Sunrise/Species/abductor.yml
+++ b/Resources/Prototypes/_Sunrise/Species/abductor.yml
@@ -11,6 +11,7 @@
defaultSkinTone: "#BFC2C7"
defaultHeight: 1.05
defaultWidth: 1.05
+ stationRecordsHidden: true
- type: bodyType
id: MobAbductorSprites