diff --git a/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml b/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml new file mode 100644 index 00000000000..9dafac9caea --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml.cs new file mode 100644 index 00000000000..0951ba55264 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/PsiWatchEntryControl.xaml.cs @@ -0,0 +1,25 @@ +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +/// +/// ADAPTED FROM SECWATCH - DELTAV +/// + +namespace Content.Client.CartridgeLoader.Cartridges; + +[GenerateTypedNameReferences] +public sealed partial class PsiWatchEntryControl : BoxContainer +{ + public PsiWatchEntryControl(PsiWatchEntry entry) + { + RobustXamlLoader.Load(this); + + Status.Text = Loc.GetString($"psionics-records-status-{entry.Status.ToString().ToLower()}"); + Title.Text = Loc.GetString("psi-watch-entry", ("name", entry.Name), ("job", entry.Job)); + + Reason.Text = entry.Reason ?? Loc.GetString("psi-watch-no-reason"); + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/PsiWatchUi.cs b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUi.cs new file mode 100644 index 00000000000..40eb2f19e43 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUi.cs @@ -0,0 +1,31 @@ +using Content.Client.UserInterface.Fragments; +using Content.Shared.CartridgeLoader; +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.UserInterface; + +/// +/// ADAPTED FROM SECWATCH - DELTAV +/// + +namespace Content.Client.CartridgeLoader.Cartridges; + +public sealed partial class PsiWatchUi : UIFragment +{ + private PsiWatchUiFragment? _fragment; + + public override Control GetUIFragmentRoot() + { + return _fragment!; + } + + public override void Setup(BoundUserInterface ui, EntityUid? owner) + { + _fragment = new PsiWatchUiFragment(); + } + + public override void UpdateState(BoundUserInterfaceState state) + { + if (state is PsiWatchUiState cast) + _fragment?.UpdateState(cast); + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml new file mode 100644 index 00000000000..25181e347b3 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml @@ -0,0 +1,13 @@ + + + + diff --git a/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml.cs new file mode 100644 index 00000000000..e446581317d --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/PsiWatchUiFragment.xaml.cs @@ -0,0 +1,29 @@ +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +/// +/// ADAPTED FROM SECWATCH - DELTAV +/// + +namespace Content.Client.CartridgeLoader.Cartridges; + +[GenerateTypedNameReferences] +public sealed partial class PsiWatchUiFragment : BoxContainer +{ + public PsiWatchUiFragment() + { + RobustXamlLoader.Load(this); + } + + public void UpdateState(PsiWatchUiState state) + { + NoEntries.Visible = state.Entries.Count == 0; + Entries.RemoveAllChildren(); + foreach (var entry in state.Entries) + { + Entries.AddChild(new PsiWatchEntryControl(entry)); + } + } +} diff --git a/Content.Client/Overlays/ShowPsionicsRecordIconsSystem.cs b/Content.Client/Overlays/ShowPsionicsRecordIconsSystem.cs new file mode 100644 index 00000000000..26f3407adf7 --- /dev/null +++ b/Content.Client/Overlays/ShowPsionicsRecordIconsSystem.cs @@ -0,0 +1,32 @@ +using Content.Shared.Overlays; +using Content.Shared.Psionics.Components; +using Content.Shared.StatusIcon; +using Content.Shared.StatusIcon.Components; +using Robust.Shared.Prototypes; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Client.Overlays; + +public sealed class ShowPsionicsRecordIconsSystem : EquipmentHudSystem +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetStatusIconsEvent); + } + + private void OnGetStatusIconsEvent(EntityUid uid, PsionicsRecordComponent component, ref GetStatusIconsEvent ev) + { + if (!IsActive) + return; + + if (_prototype.TryIndex(component.StatusIcon, out var iconPrototype)) + ev.StatusIcons.Add(iconPrototype); + } +} diff --git a/Content.Client/PsionicsRecords/Components/PsionicsRecordsConsoleSystem.cs b/Content.Client/PsionicsRecords/Components/PsionicsRecordsConsoleSystem.cs new file mode 100644 index 00000000000..8f68e38c86d --- /dev/null +++ b/Content.Client/PsionicsRecords/Components/PsionicsRecordsConsoleSystem.cs @@ -0,0 +1,11 @@ +using Content.Shared.PsionicsRecords.Systems; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Client.PsionicsRecords.Systems; + +public sealed class PsionicsRecordsConsoleSystem : SharedPsionicsRecordsConsoleSystem +{ +} diff --git a/Content.Client/PsionicsRecords/PsionicsRecordsConsoleBoundUserInterface.cs b/Content.Client/PsionicsRecords/PsionicsRecordsConsoleBoundUserInterface.cs new file mode 100644 index 00000000000..3d38f6db648 --- /dev/null +++ b/Content.Client/PsionicsRecords/PsionicsRecordsConsoleBoundUserInterface.cs @@ -0,0 +1,64 @@ +using Content.Shared.Access.Systems; +using Content.Shared.PsionicsRecords; +using Content.Shared.PsionicsRecords.Components; +using Content.Shared.Psionics; +using Content.Shared.StationRecords; +using Robust.Client.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Client.PsionicsRecords; + +public sealed class PsionicsRecordsConsoleBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + private readonly AccessReaderSystem _accessReader; + + private PsionicsRecordsConsoleWindow? _window; + + public PsionicsRecordsConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + _accessReader = EntMan.System(); + } + + protected override void Open() + { + base.Open(); + + var comp = EntMan.GetComponent(Owner); + + _window = new(Owner, comp.MaxStringLength, _playerManager, _proto, _random, _accessReader); + _window.OnKeySelected += key => + SendMessage(new SelectStationRecord(key)); + _window.OnFiltersChanged += (type, filterValue) => + SendMessage(new SetStationRecordFilter(type, filterValue)); + _window.OnStatusSelected += status => + SendMessage(new PsionicsRecordChangeStatus(status, null)); + _window.OnDialogConfirmed += (status, reason) => + SendMessage(new PsionicsRecordChangeStatus(status, reason)); + _window.OnClose += Close; + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not PsionicsRecordsConsoleState cast) + return; + + _window?.UpdateState(cast); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + _window?.Close(); + } +} diff --git a/Content.Client/PsionicsRecords/PsionicsRecordsConsoleWindow.xaml b/Content.Client/PsionicsRecords/PsionicsRecordsConsoleWindow.xaml new file mode 100644 index 00000000000..40a3a58b50b --- /dev/null +++ b/Content.Client/PsionicsRecords/PsionicsRecordsConsoleWindow.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/Content.Client/PsionicsRecords/PsionicsRecordsConsoleWindow.xaml.cs b/Content.Client/PsionicsRecords/PsionicsRecordsConsoleWindow.xaml.cs new file mode 100644 index 00000000000..2099d0aabe3 --- /dev/null +++ b/Content.Client/PsionicsRecords/PsionicsRecordsConsoleWindow.xaml.cs @@ -0,0 +1,256 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared.Access.Systems; +using Content.Shared.Administration; +using Content.Shared.PsionicsRecords; +using Content.Shared.Dataset; +using Content.Shared.Psionics; +using Content.Shared.StationRecords; +using Robust.Client.AutoGenerated; +using Robust.Client.Player; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Client.PsionicsRecords; + +// TODO: dedupe shitcode from general records theres a lot +[GenerateTypedNameReferences] +public sealed partial class PsionicsRecordsConsoleWindow : FancyWindow +{ + private readonly IPlayerManager _player; + private readonly IPrototypeManager _proto; + private readonly IRobustRandom _random; + private readonly AccessReaderSystem _accessReader; + + public readonly EntityUid Console; + + [ValidatePrototypeId] + private const string ReasonPlaceholders = "PsionicsRecordsRecordsPlaceholders"; + + public Action? OnKeySelected; + public Action? OnFiltersChanged; + public Action? OnStatusSelected; + public Action? OnDialogConfirmed; + + private uint _maxLength; + private bool _isPopulating; + private bool _access; + private uint? _selectedKey; + private PsionicsRecord? _selectedRecord; + + private DialogWindow? _reasonDialog; + + private StationRecordFilterType _currentFilterType; + + public PsionicsRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerManager playerManager, IPrototypeManager prototypeManager, IRobustRandom robustRandom, AccessReaderSystem accessReader) + { + RobustXamlLoader.Load(this); + + Console = console; + _player = playerManager; + _proto = prototypeManager; + _random = robustRandom; + _accessReader = accessReader; + + _maxLength = maxLength; + _currentFilterType = StationRecordFilterType.Name; + + OpenCentered(); + + foreach (var item in Enum.GetValues()) + { + FilterType.AddItem(GetTypeFilterLocals(item), (int) item); + } + + foreach (var status in Enum.GetValues()) + { + AddStatusSelect(status); + } + + OnClose += () => _reasonDialog?.Close(); + + RecordListing.OnItemSelected += args => + { + if (_isPopulating || RecordListing[args.ItemIndex].Metadata is not uint cast) + return; + + OnKeySelected?.Invoke(cast); + }; + + RecordListing.OnItemDeselected += _ => + { + if (!_isPopulating) + OnKeySelected?.Invoke(null); + }; + + FilterType.OnItemSelected += eventArgs => + { + var type = (StationRecordFilterType) eventArgs.Id; + + if (_currentFilterType != type) + { + _currentFilterType = type; + FilterListingOfRecords(FilterText.Text); + } + }; + + FilterText.OnTextEntered += args => + { + FilterListingOfRecords(args.Text); + }; + + StatusOptionButton.OnItemSelected += args => + { + SetStatus((PsionicsStatus) args.Id); + }; + } + + public void UpdateState(PsionicsRecordsConsoleState state) + { + if (state.Filter != null) + { + if (state.Filter.Type != _currentFilterType) + { + _currentFilterType = state.Filter.Type; + } + + if (state.Filter.Value != FilterText.Text) + { + FilterText.Text = state.Filter.Value; + } + } + + _selectedKey = state.SelectedKey; + + FilterType.SelectId((int) _currentFilterType); + + // set up the records listing panel + RecordListing.Clear(); + + var hasRecords = state.RecordListing != null && state.RecordListing.Count > 0; + NoRecords.Visible = !hasRecords; + if (hasRecords) + PopulateRecordListing(state.RecordListing!); + + // set up the selected person's record + var selected = _selectedKey != null; + + PersonContainer.Visible = selected; + RecordUnselected.Visible = !selected; + + _access = _player.LocalSession?.AttachedEntity is {} player + && _accessReader.IsAllowed(player, Console); + + // hide access-required editing parts when no access + var editing = _access && selected; + StatusOptionButton.Disabled = !editing; + + if (state is { PsionicsRecord: not null, StationRecord: not null }) + { + PopulateRecordContainer(state.StationRecord, state.PsionicsRecord); + _selectedRecord = state.PsionicsRecord; + } + else + { + _selectedRecord = null; + } + } + + private void PopulateRecordListing(Dictionary listing) + { + _isPopulating = true; + + foreach (var (key, name) in listing) + { + var item = RecordListing.AddItem(name); + item.Metadata = key; + item.Selected = key == _selectedKey; + } + _isPopulating = false; + + RecordListing.SortItemsByText(); + } + + private void PopulateRecordContainer(GeneralStationRecord stationRecord, PsionicsRecord psionicsRecord) + { + var na = Loc.GetString("generic-not-available-shorthand"); + PersonName.Text = stationRecord.Name; + + StatusOptionButton.SelectId((int) psionicsRecord.Status); + if (psionicsRecord.Reason is {} reason) + { + var message = FormattedMessage.FromMarkup(Loc.GetString("psionics-records-console-wanted-reason")); + message.AddText($": {reason}"); + PsionicsList.SetMessage(message); + PsionicsList.Visible = true; + } + else + { + PsionicsList.Visible = false; + } + } + + private void AddStatusSelect(PsionicsStatus status) + { + var name = Loc.GetString($"psionics-records-status-{status.ToString().ToLower()}"); + StatusOptionButton.AddItem(name, (int) status); + } + + private void FilterListingOfRecords(string text = "") + { + if (!_isPopulating) + { + OnFiltersChanged?.Invoke(_currentFilterType, text); + } + } + + private void SetStatus(PsionicsStatus status) + { + if (status != PsionicsStatus.None) // All statuses should have a reasoning. + { + GetReason(status); + return; + } + + OnStatusSelected?.Invoke(status); + } + + private void GetReason(PsionicsStatus status) + { + if (_reasonDialog != null) + { + _reasonDialog.MoveToFront(); + return; + } + + var field = "reason"; + var title = Loc.GetString("psionics-records-status-" + status.ToString().ToLower()); + var placeholders = _proto.Index(ReasonPlaceholders); + var placeholder = Loc.GetString("psionics-records-console-reason-placeholder", ("placeholder", _random.Pick(placeholders.Values))); // just funny it doesn't actually get used + var prompt = Loc.GetString("psionics-records-console-reason"); + var entry = new QuickDialogEntry(field, QuickDialogEntryType.LongText, prompt, placeholder); + var entries = new List() { entry }; + _reasonDialog = new DialogWindow(title, entries); + + _reasonDialog.OnConfirmed += responses => + { + var reason = responses[field]; + if (reason.Length < 1 || reason.Length > _maxLength) + return; + + OnDialogConfirmed?.Invoke(status, reason); + }; + + _reasonDialog.OnClose += () => { _reasonDialog = null; }; + } + + private string GetTypeFilterLocals(StationRecordFilterType type) + { + return Loc.GetString($"psionics-records-{type.ToString().ToLower()}-filter"); + } +} diff --git a/Content.Server/CartridgeLoader/Cartridges/PsiWatchCartridgeComponent.cs b/Content.Server/CartridgeLoader/Cartridges/PsiWatchCartridgeComponent.cs new file mode 100644 index 00000000000..66a8b131d35 --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/PsiWatchCartridgeComponent.cs @@ -0,0 +1,28 @@ +using Content.Shared.Psionics; + +/// +/// ADAPTED FROM SECWATCH - DELTAV +/// + +namespace Content.Server.CartridgeLoader.Cartridges; + +[RegisterComponent, Access(typeof(PsiWatchCartridgeSystem))] +public sealed partial class PsiWatchCartridgeComponent : Component +{ + /// + /// Only show people with these statuses. + /// + [DataField] + public List Statuses = new() + { + PsionicsStatus.Abusing, + PsionicsStatus.Registered, + PsionicsStatus.Suspected + }; + + /// + /// Station entity thats getting its records checked. + /// + [DataField] + public EntityUid? Station; +} diff --git a/Content.Server/CartridgeLoader/Cartridges/PsiWatchCartridgeSystem.cs b/Content.Server/CartridgeLoader/Cartridges/PsiWatchCartridgeSystem.cs new file mode 100644 index 00000000000..c0936e10fdb --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/PsiWatchCartridgeSystem.cs @@ -0,0 +1,77 @@ +using Content.Server.Station.Systems; +using Content.Server.StationRecords; +using Content.Server.StationRecords.Systems; +using Content.Shared.CartridgeLoader; +using Content.Shared.CartridgeLoader.Cartridges; +using Content.Shared.PsionicsRecords; +using Content.Shared.StationRecords; + +/// +/// ADAPTED FROM SECWATCH - DELTAV +/// + +namespace Content.Server.CartridgeLoader.Cartridges; + +public sealed class PsiWatchCartridgeSystem : EntitySystem +{ + [Dependency] private readonly CartridgeLoaderSystem _cartridgeLoader = default!; + [Dependency] private readonly StationRecordsSystem _records = default!; + [Dependency] private readonly StationSystem _station = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRecordModified); + + SubscribeLocalEvent(OnUiReady); + } + + private void OnRecordModified(RecordModifiedEvent args) + { + // when a record is modified update the ui of every loaded cartridge tuned to the same station + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp, out var cartridge)) + { + if (cartridge.LoaderUid is not {} loader || comp.Station != args.Station) + continue; + + UpdateUI((uid, comp), loader); + } + } + + private void OnUiReady(Entity ent, ref CartridgeUiReadyEvent args) + { + UpdateUI(ent, args.Loader); + } + + private void UpdateUI(Entity ent, EntityUid loader) + { + // if the loader is on a grid, update the station + // if it is off grid use the cached station + if (_station.GetOwningStation(loader) is {} station) + ent.Comp.Station = station; + + if (!TryComp(ent.Comp.Station, out var records)) + return; + + station = ent.Comp.Station.Value; + + var entries = new List(); + foreach (var (id, criminal) in _records.GetRecordsOfType(station, records)) + { + if (!ent.Comp.Statuses.Contains(criminal.Status)) + continue; + + var key = new StationRecordKey(id, station); + if (!_records.TryGetRecord(key, out var general, records)) + continue; + + var status = criminal.Status; + entries.Add(new PsiWatchEntry(general.Name, general.JobTitle, criminal.Status, criminal.Reason)); + } + + var state = new PsiWatchUiState(entries); + _cartridgeLoader.UpdateCartridgeUiState(loader, state); + } +} diff --git a/Content.Server/IdentityManagement/IdentitySystem.cs b/Content.Server/IdentityManagement/IdentitySystem.cs index 1a2cdcce511..e2f57b648a4 100644 --- a/Content.Server/IdentityManagement/IdentitySystem.cs +++ b/Content.Server/IdentityManagement/IdentitySystem.cs @@ -1,6 +1,7 @@ using Content.Server.Access.Systems; using Content.Server.Administration.Logs; using Content.Server.CriminalRecords.Systems; +using Content.Server.PsionicsRecords.Systems; using Content.Server.Humanoid; using Content.Shared.Clothing; using Content.Shared.Database; @@ -27,6 +28,7 @@ public sealed class IdentitySystem : SharedIdentitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; [Dependency] private readonly CriminalRecordsConsoleSystem _criminalRecordsConsole = default!; + [Dependency] private readonly PsionicsRecordsConsoleSystem _psionicsRecordsConsole = default!; private HashSet _queuedIdentityUpdates = new(); @@ -112,7 +114,7 @@ private void UpdateIdentityInfo(EntityUid uid, IdentityComponent identity) _adminLog.Add(LogType.Identity, LogImpact.Medium, $"{ToPrettyString(uid)} changed identity to {name}"); var identityChangedEvent = new IdentityChangedEvent(uid, ident); RaiseLocalEvent(uid, ref identityChangedEvent); - SetIdentityCriminalIcon(uid); + SetIdentityRecordsIcon(uid); } private string GetIdentityName(EntityUid target, IdentityRepresentation representation) @@ -124,13 +126,14 @@ private string GetIdentityName(EntityUid target, IdentityRepresentation represen } /// - /// When the identity of a person is changed, searches the criminal records to see if the name of the new identity - /// has a record. If the new name has a criminal status attached to it, the person will get the criminal status - /// until they change identity again. + /// When the identity of a person is changed, searches the criminal records and psionics records to see if the name + /// of the new identity has a record. If the new name has a criminal status or psionics status attached to it, the + /// person will get the criminal status and/or psionics status until they change identity again. /// - private void SetIdentityCriminalIcon(EntityUid uid) + private void SetIdentityRecordsIcon(EntityUid uid) { _criminalRecordsConsole.CheckNewIdentity(uid); + _psionicsRecordsConsole.CheckNewIdentity(uid); } /// diff --git a/Content.Server/PsionicsRecords/Systems/PsionicsRecordsConsoleSystem.cs b/Content.Server/PsionicsRecords/Systems/PsionicsRecordsConsoleSystem.cs new file mode 100644 index 00000000000..a847d45ce96 --- /dev/null +++ b/Content.Server/PsionicsRecords/Systems/PsionicsRecordsConsoleSystem.cs @@ -0,0 +1,234 @@ +using Content.Server.Popups; +using Content.Server.Radio.EntitySystems; +using Content.Server.Station.Systems; +using Content.Server.StationRecords; +using Content.Server.StationRecords.Systems; +using Content.Shared.Access.Systems; +using Content.Shared.PsionicsRecords; +using Content.Shared.PsionicsRecords.Components; +using Content.Shared.PsionicsRecords.Systems; +using Content.Shared.Psionics; +using Content.Shared.StationRecords; +using Robust.Server.GameObjects; +using System.Diagnostics.CodeAnalysis; +using Content.Shared.IdentityManagement; +using Content.Shared.Psionics.Components; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Server.PsionicsRecords.Systems; + +/// +/// Handles all UI for Psionics records console +/// +public sealed class PsionicsRecordsConsoleSystem : SharedPsionicsRecordsConsoleSystem +{ + [Dependency] private readonly AccessReaderSystem _access = default!; + [Dependency] private readonly PsionicsRecordsSystem _psionicsRecords = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly RadioSystem _radio = default!; + [Dependency] private readonly SharedIdCardSystem _idCard = default!; + [Dependency] private readonly StationRecordsSystem _stationRecords = default!; + [Dependency] private readonly StationSystem _station = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; + + public override void Initialize() + { + SubscribeLocalEvent(UpdateUserInterface); + SubscribeLocalEvent(UpdateUserInterface); + + Subs.BuiEvents(PsionicsRecordsConsoleKey.Key, subs => + { + subs.Event(UpdateUserInterface); + subs.Event(OnKeySelected); + subs.Event(OnFiltersChanged); + subs.Event(OnChangeStatus); + }); + } + + private void UpdateUserInterface(Entity ent, ref T args) + { + // TODO: this is probably wasteful, maybe better to send a message to modify the exact state? + UpdateUserInterface(ent); + } + + private void OnKeySelected(Entity ent, ref SelectStationRecord msg) + { + // no concern of sus client since record retrieval will fail if invalid id is given + ent.Comp.ActiveKey = msg.SelectedKey; + UpdateUserInterface(ent); + } + + private void OnFiltersChanged(Entity ent, ref SetStationRecordFilter msg) + { + if (ent.Comp.Filter == null || + ent.Comp.Filter.Type != msg.Type || ent.Comp.Filter.Value != msg.Value) + { + ent.Comp.Filter = new StationRecordsFilter(msg.Type, msg.Value); + UpdateUserInterface(ent); + } + } + + private void OnChangeStatus(Entity ent, ref PsionicsRecordChangeStatus msg) + { + // prevent malf client violating registered/reason nullability + if (msg.Status == PsionicsStatus.Registered != (msg.Reason != null) && + msg.Status == PsionicsStatus.Suspected != (msg.Reason != null) && + msg.Status == PsionicsStatus.Abusing != (msg.Reason != null)) + return; + + if (!CheckSelected(ent, msg.Actor, out var mob, out var key)) + return; + + if (!_stationRecords.TryGetRecord(key.Value, out var record) || record.Status == msg.Status) + return; + + // validate the reason + string? reason = null; + if (msg.Reason != null) + { + reason = msg.Reason.Trim(); + if (reason.Length < 1 || reason.Length > ent.Comp.MaxStringLength) + return; + } + + var oldStatus = record.Status; + + // will probably never fail given the checks above + _psionicsRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason); + + var name = RecordName(key.Value); + var officer = Loc.GetString("psionics-records-console-unknown-officer"); + + var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(null, mob.Value); + RaiseLocalEvent(tryGetIdentityShortInfoEvent); + if (tryGetIdentityShortInfoEvent.Title != null) + { + officer = tryGetIdentityShortInfoEvent.Title; + } + + (string, object)[] args; + if (reason != null) + args = new (string, object)[] { ("name", name), ("officer", officer), ("reason", reason) }; + else + args = new (string, object)[] { ("name", name), ("officer", officer) }; + + // figure out which radio message to send depending on transition + var statusString = (oldStatus, msg.Status) switch + { + // person has been registered + (_, PsionicsStatus.Registered) => "registered", + // person did something suspicious + (_, PsionicsStatus.Suspected) => "suspected", + // person is abusing + (_, PsionicsStatus.Abusing) => "abusing", + // person is no longer suspicious + (PsionicsStatus.Suspected, PsionicsStatus.None) => "not-suspected", + // person is no longer registered + (PsionicsStatus.Registered, PsionicsStatus.None) => "not-registered", + // person is no longer abusing + (PsionicsStatus.Abusing, PsionicsStatus.None) => "not-abusing", + // this is impossible + _ => "not-wanted" + }; + _radio.SendRadioMessage(ent, Loc.GetString($"psionics-records-console-{statusString}", args), + ent.Comp.RadioChannel, ent); + + UpdateUserInterface(ent); + UpdatePsionicsIdentity(name, msg.Status); + } + + private void UpdateUserInterface(Entity ent) + { + var (uid, console) = ent; + var owningStation = _station.GetOwningStation(uid); + + if (!TryComp(owningStation, out var stationRecords)) + { + _ui.SetUiState(uid, PsionicsRecordsConsoleKey.Key, new PsionicsRecordsConsoleState()); + return; + } + + var listing = _stationRecords.BuildListing((owningStation.Value, stationRecords), console.Filter); + + var state = new PsionicsRecordsConsoleState(listing, console.Filter); + if (console.ActiveKey is { } id) + { + // get records to display when a crewmember is selected + var key = new StationRecordKey(id, owningStation.Value); + _stationRecords.TryGetRecord(key, out state.StationRecord, stationRecords); + _stationRecords.TryGetRecord(key, out state.PsionicsRecord, stationRecords); + state.SelectedKey = id; + } + + _ui.SetUiState(uid, PsionicsRecordsConsoleKey.Key, state); + } + + /// + /// Boilerplate that most actions use, if they require that a record be selected. + /// Obviously shouldn't be used for selecting records. + /// + private bool CheckSelected(Entity ent, EntityUid user, + [NotNullWhen(true)] out EntityUid? mob, [NotNullWhen(true)] out StationRecordKey? key) + { + key = null; + mob = null; + + if (!_access.IsAllowed(user, ent)) + { + _popup.PopupEntity(Loc.GetString("psionics-records-permission-denied"), ent, user); + return false; + } + + if (ent.Comp.ActiveKey is not { } id) + return false; + + // checking the console's station since the user might be off-grid using on-grid console + if (_station.GetOwningStation(ent) is not { } station) + return false; + + key = new StationRecordKey(id, station); + mob = user; + return true; + } + + /// + /// Gets the name from a record, or empty string if this somehow fails. + /// + private string RecordName(StationRecordKey key) + { + if (!_stationRecords.TryGetRecord(key, out var record)) + return ""; + + return record.Name; + } + + /// + /// Checks if the new identity's name has a psionics record attached to it, and gives the entity the icon that + /// belongs to the status if it does. + /// + public void CheckNewIdentity(EntityUid uid) + { + var name = Identity.Name(uid, EntityManager); + var xform = Transform(uid); + + // TODO use the entity's station? Not the station of the map that it happens to currently be on? + var station = _station.GetStationInMap(xform.MapID); + + if (station != null && _stationRecords.GetRecordByName(station.Value, name) is { } id) + { + if (_stationRecords.TryGetRecord(new StationRecordKey(id, station.Value), + out var record)) + { + if (record.Status != PsionicsStatus.None) + { + SetPsionicsIcon(name, record.Status, uid); + return; + } + } + } + RemComp(uid); + } +} diff --git a/Content.Server/PsionicsRecords/Systems/PsionicsRecordsSystem.cs b/Content.Server/PsionicsRecords/Systems/PsionicsRecordsSystem.cs new file mode 100644 index 00000000000..c30851316fa --- /dev/null +++ b/Content.Server/PsionicsRecords/Systems/PsionicsRecordsSystem.cs @@ -0,0 +1,59 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Server.StationRecords.Systems; +using Content.Shared.PsionicsRecords; +using Content.Shared.Psionics; +using Content.Shared.StationRecords; +using Content.Server.GameTicking; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Server.PsionicsRecords.Systems; + +/// +/// Psionics records +/// +/// Psionics Records inherit Station Records' core and add role-playing tools for Epistemics: +/// - Ability to track a person's status (None/Suspected/Registered/Abusing) +/// - See cataloguers' actions in Psionics Records in the radio +/// - See reasons for any action with no need to ask the officer personally +/// +public sealed class PsionicsRecordsSystem : EntitySystem +{ + [Dependency] private readonly GameTicker _ticker = default!; + [Dependency] private readonly StationRecordsSystem _stationRecords = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGeneralRecordCreated); + } + + private void OnGeneralRecordCreated(AfterGeneralRecordCreatedEvent ev) + { + _stationRecords.AddRecordEntry(ev.Key, new PsionicsRecord()); + _stationRecords.Synchronize(ev.Key); + } + + /// + /// Tries to change the status of the record found by the StationRecordKey. + /// Reason should only be passed if status is not None. + /// + /// True if the status is changed, false if not + public bool TryChangeStatus(StationRecordKey key, PsionicsStatus status, string? reason) + { + // don't do anything if its the same status + if (!_stationRecords.TryGetRecord(key, out var record) + || status == record.Status) + return false; + + record.Status = status; + record.Reason = reason; + + _stationRecords.Synchronize(key); + + return true; + } +} diff --git a/Content.Shared/CartridgeLoader/Cartridges/PsiWatchUiState.cs b/Content.Shared/CartridgeLoader/Cartridges/PsiWatchUiState.cs new file mode 100644 index 00000000000..7dae7df5a63 --- /dev/null +++ b/Content.Shared/CartridgeLoader/Cartridges/PsiWatchUiState.cs @@ -0,0 +1,24 @@ +using Content.Shared.Psionics; +using Robust.Shared.Serialization; + +namespace Content.Shared.CartridgeLoader.Cartridges; + +/// +/// Show a list of wanted and suspected people from psionics records. +/// +[Serializable, NetSerializable] +public sealed class PsiWatchUiState : BoundUserInterfaceState +{ + public readonly List Entries; + + public PsiWatchUiState(List entries) + { + Entries = entries; + } +} + +/// +/// Entry for a person who is suspected, registered, or abusing. +/// +[Serializable, NetSerializable] +public record struct PsiWatchEntry(string Name, string Job, PsionicsStatus Status, string? Reason); diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index 4375f1ab193..6d9523e8511 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -65,6 +65,7 @@ public void InitializeRelay() SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); + SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); diff --git a/Content.Shared/Overlays/ShowPsionicsRecordIconsSystem.cs b/Content.Shared/Overlays/ShowPsionicsRecordIconsSystem.cs new file mode 100644 index 00000000000..3562ea08088 --- /dev/null +++ b/Content.Shared/Overlays/ShowPsionicsRecordIconsSystem.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Shared.Overlays; + +/// +/// This component allows you to see Psionics record status of mobs. +/// + +[RegisterComponent, NetworkedComponent] +public sealed partial class ShowPsionicsRecordIconsComponent : Component { } diff --git a/Content.Shared/Psionics/Components/PsionicsRecordComponent.cs b/Content.Shared/Psionics/Components/PsionicsRecordComponent.cs new file mode 100644 index 00000000000..5884410ebda --- /dev/null +++ b/Content.Shared/Psionics/Components/PsionicsRecordComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.StatusIcon; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Shared.Psionics.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PsionicsRecordComponent : Component +{ + /// + /// The icon that should be displayed based on the psionics status of the entity. + /// + [DataField, AutoNetworkedField] + public ProtoId StatusIcon = "PsionicsIconStatus"; +} diff --git a/Content.Shared/Psionics/PsionicsStatus.cs b/Content.Shared/Psionics/PsionicsStatus.cs new file mode 100644 index 00000000000..59530915763 --- /dev/null +++ b/Content.Shared/Psionics/PsionicsStatus.cs @@ -0,0 +1,21 @@ +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Shared.Psionics; + +/// +/// Status used in Psionics Records. +/// +/// None - the default value +/// Suspected - the person is suspected of having psionics +/// Registered - the person is a registered psionics user +/// Abusing - the person has been caught abusing their psionics +/// +public enum PsionicsStatus : byte +{ + None, + Suspected, + Registered, + Abusing +} diff --git a/Content.Shared/PsionicsRecords/Components/SharedPsionicsRecordsConsoleComponent.cs b/Content.Shared/PsionicsRecords/Components/SharedPsionicsRecordsConsoleComponent.cs new file mode 100644 index 00000000000..e8f38073fe9 --- /dev/null +++ b/Content.Shared/PsionicsRecords/Components/SharedPsionicsRecordsConsoleComponent.cs @@ -0,0 +1,49 @@ +using Content.Shared.PsionicsRecords.Systems; +using Content.Shared.Radio; +using Content.Shared.StationRecords; +using Robust.Shared.Prototypes; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Shared.PsionicsRecords.Components; + +/// +/// A component for Psionics Records Console storing an active station record key and a currently applied filter +/// +[RegisterComponent] +[Access(typeof(SharedPsionicsRecordsConsoleSystem))] +public sealed partial class PsionicsRecordsConsoleComponent : Component +{ + /// + /// Currently active station record key. + /// There is no station parameter as the console uses the current station. + /// + /// + /// TODO: in the future this should be clientside instead of something players can fight over. + /// Client selects a record and tells the server the key it wants records for. + /// Server then sends a state with just the records, not the listing or filter, and the client updates just that. + /// I don't know if it's possible to have multiple bui states right now. + /// + [DataField] + public uint? ActiveKey; + + /// + /// Currently applied filter. + /// + [DataField] + public StationRecordsFilter? Filter; + + /// + /// Channel to send messages to when someone's status gets changed. + /// + [DataField] + public ProtoId RadioChannel = "Science"; + + /// + /// Max length of psionics listing strings. + /// + [DataField] + public uint MaxStringLength = 256; +} diff --git a/Content.Shared/PsionicsRecords/PsionicsRecord.cs b/Content.Shared/PsionicsRecords/PsionicsRecord.cs new file mode 100644 index 00000000000..fd68775e410 --- /dev/null +++ b/Content.Shared/PsionicsRecords/PsionicsRecord.cs @@ -0,0 +1,29 @@ +using Content.Shared.Psionics; +using Robust.Shared.Serialization; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Shared.PsionicsRecords; + +/// +/// Psionics record for a crewmember. +/// Can be viewed and edited in a psionics records console by epistemics. +/// +[Serializable, NetSerializable, DataRecord] +public sealed record PsionicsRecord +{ + /// + /// Status of the person (None, Suspect, Registered, Abusing). + /// + [DataField] + public PsionicsStatus Status = PsionicsStatus.None; + + /// + /// When Status is Anything but none, the reason for it. + /// Should never be set otherwise. + /// + [DataField] + public string? Reason; +} diff --git a/Content.Shared/PsionicsRecords/PsionicsRecordsUi.cs b/Content.Shared/PsionicsRecords/PsionicsRecordsUi.cs new file mode 100644 index 00000000000..0e69f2bb6c0 --- /dev/null +++ b/Content.Shared/PsionicsRecords/PsionicsRecordsUi.cs @@ -0,0 +1,78 @@ +using Content.Shared.Psionics; +using Content.Shared.StationRecords; +using Robust.Shared.Serialization; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Shared.PsionicsRecords; + +[Serializable, NetSerializable] +public enum PsionicsRecordsConsoleKey : byte +{ + Key +} + +/// +/// Psionics records console state. There are a few states: +/// - SelectedKey null, Record null, RecordListing null +/// - The station record database could not be accessed. +/// - SelectedKey null, Record null, RecordListing non-null +/// - Records are populated in the database, or at least the station has +/// the correct component. +/// - SelectedKey non-null, Record null, RecordListing non-null +/// - The selected key does not have a record tied to it. +/// - SelectedKey non-null, Record non-null, RecordListing non-null +/// - The selected key has a record tied to it, and the record has been sent. +/// +/// - there is added new filters and so added new states +/// -SelectedKey null, Record null, RecordListing null, filters non-null +/// the station may have data, but they all did not pass through the filters +/// +/// Other states are erroneous. +/// +[Serializable, NetSerializable] +public sealed class PsionicsRecordsConsoleState : BoundUserInterfaceState +{ + /// + /// Currently selected crewmember record key. + /// + public uint? SelectedKey = null; + + public PsionicsRecord? PsionicsRecord = null; + public GeneralStationRecord? StationRecord = null; + public readonly Dictionary? RecordListing; + public readonly StationRecordsFilter? Filter; + + public PsionicsRecordsConsoleState(Dictionary? recordListing, StationRecordsFilter? newFilter) + { + RecordListing = recordListing; + Filter = newFilter; + } + + /// + /// Default state for opening the console + /// + public PsionicsRecordsConsoleState() : this(null, null) + { + } + + public bool IsEmpty() => SelectedKey == null && StationRecord == null && PsionicsRecord == null && RecordListing == null; +} + +/// +/// Used to change status, respecting the psionics nullability rules in . +/// +[Serializable, NetSerializable] +public sealed class PsionicsRecordChangeStatus : BoundUserInterfaceMessage +{ + public readonly PsionicsStatus Status; + public readonly string? Reason; + + public PsionicsRecordChangeStatus(PsionicsStatus status, string? reason) + { + Status = status; + Reason = reason; + } +} diff --git a/Content.Shared/PsionicsRecords/Systems/SharedPsionicsRecordsConsoleSystem.cs b/Content.Shared/PsionicsRecords/Systems/SharedPsionicsRecordsConsoleSystem.cs new file mode 100644 index 00000000000..21867e65a02 --- /dev/null +++ b/Content.Shared/PsionicsRecords/Systems/SharedPsionicsRecordsConsoleSystem.cs @@ -0,0 +1,54 @@ +using Content.Shared.IdentityManagement; +using Content.Shared.IdentityManagement.Components; +using Content.Shared.Psionics; +using Content.Shared.Psionics.Components; + +/// +/// EVERYTHING HERE IS A MODIFIED VERSION OF CRIMINAL RECORDS +/// + +namespace Content.Shared.PsionicsRecords.Systems; + +public abstract class SharedPsionicsRecordsConsoleSystem : EntitySystem +{ + /// + /// Any entity that has the name of the record that was just changed as their visible name will get their icon + /// updated with the new status, if the record got removed their icon will be removed too. + /// + public void UpdatePsionicsIdentity(string name, PsionicsStatus status) + { + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var identity)) + { + if (!Identity.Name(uid, EntityManager).Equals(name)) + continue; + + if (status == PsionicsStatus.None) + RemComp(uid); + else + SetPsionicsIcon(name, status, uid); + } + } + + /// + /// Decides the icon that should be displayed on the entity based on the psionics status + /// + public void SetPsionicsIcon(string name, PsionicsStatus status, EntityUid characterUid) + { + EnsureComp(characterUid, out var record); + + var previousIcon = record.StatusIcon; + + record.StatusIcon = status switch + { + PsionicsStatus.Suspected => "PsionicsIconSuspected", + PsionicsStatus.Registered => "PsionicsIconRegistered", + PsionicsStatus.Abusing => "PsionicsIconAbusing", + _ => record.StatusIcon + }; + + if (previousIcon != record.StatusIcon) + Dirty(characterUid, record); + } +} diff --git a/Content.Shared/StatusIcon/StatusIconPrototype.cs b/Content.Shared/StatusIcon/StatusIconPrototype.cs index 689bec3882b..a9ec1f7098e 100644 --- a/Content.Shared/StatusIcon/StatusIconPrototype.cs +++ b/Content.Shared/StatusIcon/StatusIconPrototype.cs @@ -163,6 +163,22 @@ public sealed partial class SecurityIconPrototype : StatusIconPrototype, IInheri public bool Abstract { get; } } +/// +/// StatusIcons for showing the psionics status on the epi HUD +/// +[Prototype] +public sealed partial class PsionicsIconPrototype : StatusIconPrototype, IInheritingPrototype +{ + /// + [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] + public string[]? Parents { get; } + + /// + [NeverPushInheritance] + [AbstractDataField] + public bool Abstract { get; } +} + /// /// StatusIcons for faction membership /// diff --git a/Resources/Locale/en-US/cartridge-loader/psiwatch.ftl b/Resources/Locale/en-US/cartridge-loader/psiwatch.ftl new file mode 100644 index 00000000000..33df99fd44f --- /dev/null +++ b/Resources/Locale/en-US/cartridge-loader/psiwatch.ftl @@ -0,0 +1,5 @@ +psi-watch-program-name = PsiWatch +psi-watch-title = PsiWatch 1.3 +psi-watch-no-entries = Nobody is registered. Why not enjoy a nice, cold brew? +psi-watch-entry = {$name}, {$job} +psi-watch-no-reason = [ERROR] No reason given? diff --git a/Resources/Locale/en-US/psionics-records/psionics-records.ftl b/Resources/Locale/en-US/psionics-records/psionics-records.ftl new file mode 100644 index 00000000000..ff2c2b8a4c5 --- /dev/null +++ b/Resources/Locale/en-US/psionics-records/psionics-records.ftl @@ -0,0 +1,37 @@ +psionics-records-console-window-title = Psionics Registry Records +psionics-records-console-records-list-title = Crewmembers +psionics-records-console-select-record-info = Select a record. +psionics-records-console-no-records = No records found! +psionics-records-console-no-record-found = No record was found for the selected person. + +## Status + +psionics-records-console-status = Status +psionics-records-status-none = None +psionics-records-status-registered = Registered Psionic +psionics-records-status-suspected = Suspected Psionics +psionics-records-status-abusing = Abusing Psionics + +psionics-records-console-wanted-reason = [color=gray]Psionics Listed[/color] +psionics-records-console-suspected-reason = [color=gray]Suspected Reason[/color] +psionics-records-console-reason = Psionics/Reason +psionics-records-console-reason-placeholder = For example: {$placeholder} + +psionics-records-permission-denied = Permission denied + +## Security channel notifications + +psionics-records-console-registered = {$name} is registered as psionic by {$officer}: {$reason}. +psionics-records-console-suspected = {$officer} marked {$name} as a possible psionic because of: {$reason}. +psionics-records-console-not-suspected = {$name} is no longer a suspected psionic. +psionics-records-console-not-registered = {$name} is no longer registered as psionic. +psionics-records-console-abusing = {$officer} marked {$name} as abusing psionics because of: {$reason}. +psionics-records-console-not-abusing = {$name} is no longer marked as abusing psionics. +psionics-records-console-unknown-officer = + +## Filters + +psionics-records-filter-placeholder = Input text and press "Enter" +psionics-records-name-filter = Name +psionics-records-prints-filter = Fingerprints +psionics-records-dna-filter = DNA diff --git a/Resources/Maps/arena.yml b/Resources/Maps/arena.yml index aa61c9bb3af..be3a8428849 100644 --- a/Resources/Maps/arena.yml +++ b/Resources/Maps/arena.yml @@ -58311,6 +58311,14 @@ entities: rot: 1.5707963267948966 rad pos: 50.5,7.5 parent: 6747 +- proto: ComputerPsionicsRecords + entities: + - uid: 27824 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 22.5,-62.5 + parent: 6747 - proto: ComputerRadar entities: - uid: 3976 diff --git a/Resources/Maps/asterisk.yml b/Resources/Maps/asterisk.yml index 41fc0f2e73d..4966832b241 100644 --- a/Resources/Maps/asterisk.yml +++ b/Resources/Maps/asterisk.yml @@ -25465,6 +25465,14 @@ entities: rot: 3.141592653589793 rad pos: 30.5,6.5 parent: 2 +- proto: ComputerPsionicsRecords + entities: + - uid: 11188 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -28.5,-18.5 + parent: 2 - proto: ComputerRadar entities: - uid: 10761 diff --git a/Resources/Maps/core.yml b/Resources/Maps/core.yml index 9685efd2e13..8284ed39610 100644 --- a/Resources/Maps/core.yml +++ b/Resources/Maps/core.yml @@ -61680,6 +61680,13 @@ entities: - type: Transform pos: -1.5,-11.5 parent: 2 +- proto: ComputerPsionicsRecords + entities: + - uid: 22546 + components: + - type: Transform + pos: 60.5,-11.5 + parent: 2 - proto: ComputerRadar entities: - uid: 4074 diff --git a/Resources/Maps/edge.yml b/Resources/Maps/edge.yml index f19c2065af2..4c1274f61d1 100644 --- a/Resources/Maps/edge.yml +++ b/Resources/Maps/edge.yml @@ -5356,7 +5356,8 @@ entities: -12,7: 0: 65535 -11,7: - 0: 65535 + 0: 57343 + 3: 8192 -11,-8: 0: 53198 -10,-8: @@ -5451,11 +5452,11 @@ entities: 0: 65535 1,-12: 0: 61937 - 3: 14 - 4: 3584 + 4: 14 + 5: 3584 1,-11: 0: 65521 - 5: 14 + 6: 14 1,-10: 0: 65535 2,-12: @@ -5560,15 +5561,15 @@ entities: 0: 63487 0,-14: 0: 65522 - 3: 12 + 4: 12 0,-13: 0: 65535 1,-14: 0: 65521 1,-13: 0: 61937 - 3: 14 - 6: 3584 + 4: 14 + 7: 3584 2,-14: 0: 12848 2,-13: @@ -5937,7 +5938,7 @@ entities: 0: 1908 -16,7: 0: 40704 - 3: 24576 + 4: 24576 -15,7: 0: 65280 -12,10: @@ -5954,7 +5955,7 @@ entities: 0: 65535 -16,9: 0: 65439 - 3: 96 + 4: 96 -16,10: 0: 231 -15,8: @@ -6091,17 +6092,17 @@ entities: 0: 4095 5,-12: 0: 883 - 3: 3212 + 4: 3212 3,-13: 0: 51200 4,-13: 0: 65392 - 3: 128 + 4: 128 5,-13: 0: 4096 - 3: 59184 + 4: 59184 6,-12: - 3: 305 + 4: 305 uniqueMixes: - volume: 2500 temperature: 293.15 @@ -6148,6 +6149,21 @@ entities: - 0 - 0 - 0 + - volume: 2500 + temperature: 293.14975 + moles: + - 20.078888 + - 75.53487 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 - volume: 2500 temperature: 293.15 moles: @@ -39459,6 +39475,14 @@ entities: rot: -1.5707963267948966 rad pos: 0.5,-25.5 parent: 2 +- proto: ComputerPsionicsRecords + entities: + - uid: 17482 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -42.5,32.5 + parent: 2 - proto: ComputerRadar entities: - uid: 6157 @@ -40235,6 +40259,24 @@ entities: - type: Transform pos: -42.5,31.5 parent: 2 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14673 + moles: + - 1.7459903 + - 6.568249 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 - proto: CrateScienceSecure entities: - uid: 6273 diff --git a/Resources/Maps/europa.yml b/Resources/Maps/europa.yml index 0d54891dc6a..c642efb66c9 100644 --- a/Resources/Maps/europa.yml +++ b/Resources/Maps/europa.yml @@ -34896,12 +34896,6 @@ entities: rot: 1.5707963267948966 rad pos: 6.5,33.5 parent: 1 - - uid: 299 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.5,17.5 - parent: 1 - uid: 455 components: - type: Transform @@ -37855,6 +37849,14 @@ entities: rot: 3.141592653589793 rad pos: -19.5,-24.5 parent: 1 +- proto: ComputerPsionicsRecords + entities: + - uid: 299 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,17.5 + parent: 1 - proto: ComputerRadar entities: - uid: 3026 @@ -65764,7 +65766,7 @@ entities: pos: -11.5,11.5 parent: 1 - type: Door - secondsUntilStateChange: -26515.367 + secondsUntilStateChange: -27843.273 state: Opening - type: Occluder enabled: True diff --git a/Resources/Maps/gaxstation.yml b/Resources/Maps/gaxstation.yml index f502c4b698d..cada7f3a9d0 100644 --- a/Resources/Maps/gaxstation.yml +++ b/Resources/Maps/gaxstation.yml @@ -53573,6 +53573,14 @@ entities: - type: Transform pos: -31.5,29.5 parent: 2 +- proto: ComputerPsionicsRecords + entities: + - uid: 20872 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 50.5,26.5 + parent: 2 - proto: ComputerRadar entities: - uid: 7211 @@ -134450,7 +134458,7 @@ entities: lastSignals: DoorStatus: True - type: Door - secondsUntilStateChange: -120542.65 + secondsUntilStateChange: -120749.305 state: Opening - uid: 18112 components: diff --git a/Resources/Maps/glacier.yml b/Resources/Maps/glacier.yml index c0fcbe15321..437e8ede164 100644 --- a/Resources/Maps/glacier.yml +++ b/Resources/Maps/glacier.yml @@ -46640,6 +46640,14 @@ entities: - type: Transform pos: 35.5,50.5 parent: 2 +- proto: ComputerPsionicsRecords + entities: + - uid: 17501 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 29.5,-21.5 + parent: 2 - proto: ComputerRadar entities: - uid: 17129 diff --git a/Resources/Maps/hammurabi.yml b/Resources/Maps/hammurabi.yml index 09f874f94b9..d360afe201c 100644 --- a/Resources/Maps/hammurabi.yml +++ b/Resources/Maps/hammurabi.yml @@ -88000,6 +88000,14 @@ entities: rot: 3.141592653589793 rad pos: -114.5,-89.5 parent: 1 +- proto: ComputerPsionicsRecords + entities: + - uid: 42455 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -19.5,-41.5 + parent: 1 - proto: ComputerRadar entities: - uid: 8226 diff --git a/Resources/Maps/hive.yml b/Resources/Maps/hive.yml index ef0019b3454..d24bf87ea5e 100644 --- a/Resources/Maps/hive.yml +++ b/Resources/Maps/hive.yml @@ -60757,6 +60757,14 @@ entities: - type: Transform pos: -96.5,5.5 parent: 1 +- proto: ComputerPsionicsRecords + entities: + - uid: 27792 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,42.5 + parent: 1 - proto: ComputerRadar entities: - uid: 5888 diff --git a/Resources/Maps/lighthouse.yml b/Resources/Maps/lighthouse.yml index 9921bbb9f6d..0ee3064aba6 100644 --- a/Resources/Maps/lighthouse.yml +++ b/Resources/Maps/lighthouse.yml @@ -44908,6 +44908,13 @@ entities: - type: Transform pos: -7.5,-39.5 parent: 100 +- proto: ComputerPsionicsRecords + entities: + - uid: 20901 + components: + - type: Transform + pos: -21.5,-6.5 + parent: 100 - proto: ComputerResearchAndDevelopment entities: - uid: 5793 diff --git a/Resources/Maps/meta.yml b/Resources/Maps/meta.yml index 30d6d9beb60..06f43cb9603 100644 --- a/Resources/Maps/meta.yml +++ b/Resources/Maps/meta.yml @@ -10339,7 +10339,7 @@ entities: pos: -53.5,-12.5 parent: 2 - type: Door - secondsUntilStateChange: -12876.496 + secondsUntilStateChange: -13237.977 state: Opening - type: DeviceLinkSource lastSignals: @@ -63769,6 +63769,14 @@ entities: rot: 3.141592653589793 rad pos: 110.5,-0.5 parent: 2 +- proto: ComputerPsionicsRecords + entities: + - uid: 27708 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,-25.5 + parent: 2 - proto: ComputerRadar entities: - uid: 10045 @@ -75688,7 +75696,7 @@ entities: pos: -3.5,-52.5 parent: 2 - type: Door - secondsUntilStateChange: -40902.03 + secondsUntilStateChange: -41263.51 state: Closing - uid: 11944 components: diff --git a/Resources/Maps/pebble.yml b/Resources/Maps/pebble.yml index 9b7c428b513..1d468237f28 100644 --- a/Resources/Maps/pebble.yml +++ b/Resources/Maps/pebble.yml @@ -29532,6 +29532,14 @@ entities: rot: 3.141592653589793 rad pos: 37.5,14.5 parent: 2 +- proto: ComputerPsionicsRecords + entities: + - uid: 10854 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,28.5 + parent: 2 - proto: ComputerRadar entities: - uid: 4588 diff --git a/Resources/Maps/radstation.yml b/Resources/Maps/radstation.yml index f350c67478e..ed00efa981c 100644 --- a/Resources/Maps/radstation.yml +++ b/Resources/Maps/radstation.yml @@ -15129,7 +15129,7 @@ entities: pos: 34.5,-35.5 parent: 2 - type: Door - secondsUntilStateChange: -55586.55 + secondsUntilStateChange: -55771.664 state: Opening - uid: 383 components: @@ -62312,6 +62312,14 @@ entities: - type: Transform pos: -12.5,-55.5 parent: 2 +- proto: ComputerPsionicsRecords + entities: + - uid: 22318 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -5.5,-28.5 + parent: 2 - proto: ComputerRadar entities: - uid: 9048 @@ -157782,7 +157790,7 @@ entities: links: - 19976 - type: Door - secondsUntilStateChange: -191878.86 + secondsUntilStateChange: -192063.97 state: Opening - uid: 23356 components: diff --git a/Resources/Maps/saltern.yml b/Resources/Maps/saltern.yml index e2f8a808534..b681e6923da 100644 --- a/Resources/Maps/saltern.yml +++ b/Resources/Maps/saltern.yml @@ -4332,7 +4332,7 @@ entities: pos: 3.5,22.5 parent: 31 - type: Door - secondsUntilStateChange: -32634.607 + secondsUntilStateChange: -32743.814 state: Opening - type: DeviceLinkSource lastSignals: @@ -29782,6 +29782,14 @@ entities: rot: -1.5707963267948966 rad pos: 60.5,3.5 parent: 31 +- proto: ComputerPsionicsRecords + entities: + - uid: 11412 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -14.5,-22.5 + parent: 31 - proto: ComputerRadar entities: - uid: 579 diff --git a/Resources/Maps/shoukou.yml b/Resources/Maps/shoukou.yml index 4cbe47174af..d75e9c6c75b 100644 --- a/Resources/Maps/shoukou.yml +++ b/Resources/Maps/shoukou.yml @@ -30445,6 +30445,14 @@ entities: rot: 1.5707963267948966 rad pos: 42.5,-35.5 parent: 34 +- proto: ComputerPsionicsRecords + entities: + - uid: 13763 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -30.5,-11.5 + parent: 34 - proto: ComputerRadar entities: - uid: 5579 @@ -36708,7 +36716,7 @@ entities: pos: -36.5,-22.5 parent: 34 - type: Door - secondsUntilStateChange: -67401.266 + secondsUntilStateChange: -68431.75 state: Closing - uid: 1274 components: diff --git a/Resources/Maps/submarine.yml b/Resources/Maps/submarine.yml index 0890fe73aa5..123cc527dc1 100644 --- a/Resources/Maps/submarine.yml +++ b/Resources/Maps/submarine.yml @@ -100515,6 +100515,14 @@ entities: - type: Transform pos: 87.5,-19.5 parent: 2 +- proto: ComputerPsionicsRecords + entities: + - uid: 39308 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 32.5,28.5 + parent: 2 - proto: ComputerRadar entities: - uid: 6359 diff --git a/Resources/Maps/tortuga.yml b/Resources/Maps/tortuga.yml index f9f702c820c..df56bcd11fd 100644 --- a/Resources/Maps/tortuga.yml +++ b/Resources/Maps/tortuga.yml @@ -63598,6 +63598,14 @@ entities: rot: 1.5707963267948966 rad pos: 48.5,28.5 parent: 33 +- proto: ComputerPsionicsRecords + entities: + - uid: 28381 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -64.5,-6.5 + parent: 33 - proto: ComputerRadar entities: - uid: 3325 diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index 9f27ee5700a..437a12aed06 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -242,6 +242,7 @@ - id: Intellicard - id: LunchboxCommandFilledRandom # Delta-V Lunchboxes! prob: 0.3 + - id: PsiWatchCartridge - type: entity id: LockerResearchDirectorFilled @@ -264,6 +265,7 @@ - id: EncryptionKeyBinary - id: LunchboxCommandFilledRandom # Delta-V Lunchboxes! prob: 0.3 + - id: PsiWatchCartridge - type: entity id: LockerHeadOfSecurityFilledHardsuit diff --git a/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/cataloger.yml b/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/cataloger.yml index 2e53ad71dac..d94c971e836 100644 --- a/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/cataloger.yml +++ b/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/cataloger.yml @@ -25,11 +25,15 @@ - type: loadout id: LoadoutCatalogerPillCanisterSpaceDrugs -#- type: characterItemGroup -# id: LoadoutCatalogerEyes -# maxItems: 1 -# items: -# +- type: characterItemGroup + id: LoadoutCatalogerEyes + maxItems: 1 + items: + - type: loadout + id: LoadoutCatalogerEyesEpiHUD + - type: loadout + id: LoadoutCatalogerEyesEpiGlasses + #- type: characterItemGroup # id: LoadoutCatalogerGloves # maxItems: 1 diff --git a/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/chaplain.yml b/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/chaplain.yml index ff3d75be33c..3d44c7211dc 100644 --- a/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/chaplain.yml +++ b/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/chaplain.yml @@ -29,11 +29,15 @@ - type: loadout id: LoadoutChaplainPillCanisterSpaceDrugs -#- type: characterItemGroup -# id: LoadoutChaplainEyes -# maxItems: 1 -# items: -# +- type: characterItemGroup + id: LoadoutChaplainEyes + maxItems: 1 + items: + - type: loadout + id: LoadoutChaplainEyesEpiHUD + - type: loadout + id: LoadoutChaplainEyesEpiGlasses + #- type: characterItemGroup # id: LoadoutChaplainGloves # maxItems: 1 diff --git a/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/mystagogue.yml b/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/mystagogue.yml index 7e2fbbe4b6a..7bf929a8853 100644 --- a/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/mystagogue.yml +++ b/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/mystagogue.yml @@ -31,11 +31,15 @@ - type: loadout id: LoadoutMystagoguePillCanisterSpaceDrugs -#- type: characterItemGroup -# id: LoadoutMystagogueEyes -# maxItems: 1 -# items: -# +- type: characterItemGroup + id: LoadoutMystagogueEyes + maxItems: 1 + items: + - type: loadout + id: LoadoutMystagogueEyesEpiHUD + - type: loadout + id: LoadoutMystagogueEyesEpiGlasses + #- type: characterItemGroup # id: LoadoutMystagogueGloves # maxItems: 1 diff --git a/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/psionicMantis.yml b/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/psionicMantis.yml index 32660c5f0bf..38bdd91206f 100644 --- a/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/psionicMantis.yml +++ b/Resources/Prototypes/CharacterItemGroups/Jobs/Epistemics/psionicMantis.yml @@ -27,11 +27,15 @@ - type: loadout id: LoadoutPsionicMantisPillCanisterCryptobiolin -#- type: characterItemGroup -# id: LoadoutPsionicMantisEyes -# maxItems: 1 -# items: -# +- type: characterItemGroup + id: LoadoutPsionicMantisEyes + maxItems: 1 + items: + - type: loadout + id: LoadoutPsionicMantisEyesEpiHUD + - type: loadout + id: LoadoutPsionicMantisEyesEpiGlasses + #- type: characterItemGroup # id: LoadoutPsionicMantisGloves # maxItems: 1 diff --git a/Resources/Prototypes/Datasets/psionic-records.yml b/Resources/Prototypes/Datasets/psionic-records.yml new file mode 100644 index 00000000000..9333b09831a --- /dev/null +++ b/Resources/Prototypes/Datasets/psionic-records.yml @@ -0,0 +1,15 @@ +# "funny" placeholders of extremely minor/non-crimes for wanted reason dialog +- type: dataset + id: PsionicsRecordsRecordsPlaceholders + values: + - Mindswapped with a rat 2 billion times + - Ascended into the telepathic realm with mind control + - Forced a clown to punch himself + - Forced a mime to speak in owo accent + - Mindcontrolled Captain to say bad words + - Pissed off the Oracle + - Became a Psionic + - Usurped the throne + - Mindswapped the Mantis with a shoe + - Drew stick figures in the Chaplain's bible + - Revived a cockroach diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml index 6231663ac79..1018f8006ea 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml @@ -231,6 +231,25 @@ - type: IdentityBlocker coverage: EYES +- type: entity + parent: [ClothingEyesBase, ShowPsionicsIcons] + id: ClothingEyesGlassesEpistemics + name: epi psionics glasses + description: Upgraded sunglasses that provide flash immunity and a epistemics psionics HUD. + components: + - type: Sprite + sprite: Clothing/Eyes/Glasses/epiglasses.rsi + - type: Clothing + sprite: Clothing/Eyes/Glasses/epiglasses.rsi + - type: FlashImmunity + - type: EyeProtection + protectionTime: 5 + - type: Tag + tags: + - WhitelistChameleon + - type: IdentityBlocker + coverage: EYES + - type: entity parent: ClothingEyesGlassesCheapSunglasses id: ClothingEyesGlassesCheapSunglassesAviator diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml b/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml index a4ba51b4133..dcf3c02e424 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml @@ -15,6 +15,13 @@ - type: ShowHealthBars - type: ShowHealthIcons +- type: entity + id: ShowPsionicsIcons + abstract: true + categories: [ HideSpawnMenu ] + components: + - type: ShowPsionicsRecordIcons + - type: entity parent: ClothingEyesBase id: ClothingEyesHudDiagnostic @@ -64,6 +71,17 @@ tags: - HudSecurity +- type: entity + parent: [ClothingEyesBase, ShowPsionicsIcons] + id: ClothingEyesHudEpistemics + name: epi psionics hud + description: A heads-up display that scans the humanoids in view and provides accurate data about their psionics records. + components: + - type: Sprite + sprite: Clothing/Eyes/Hud/epi.rsi + - type: Clothing + sprite: Clothing/Eyes/Hud/epi.rsi + - type: entity parent: ClothingEyesBase id: ClothingEyesHudBeer @@ -170,7 +188,7 @@ damageContainers: - Biological - type: ShowHealthIcons - damageContainers: + damageContainers: - Biological - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index 93fc8128851..520f830e806 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -76,6 +76,17 @@ - type: ComputerBoard prototype: ComputerCriminalRecords +- type: entity + parent: BaseComputerCircuitboard + id: PsionicsRecordsComputerCircuitboard + name: psionics registry computer board + description: A computer printed circuit board for a psionics registry computer. + components: + - type: Sprite + state: cpu_science + - type: ComputerBoard + prototype: ComputerPsionicsRecords + - type: entity parent: BaseComputerCircuitboard id: StationRecordsComputerCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml index e9108fd341b..e8c6816feab 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml @@ -131,3 +131,25 @@ sprite: Objects/Devices/gps.rsi state: icon - type: AstroNavCartridge + + +- type: entity + parent: BaseItem + id: PsiWatchCartridge + name: psi watch cartridge + description: A cartridge that tracks the status of currently documented psionics individuals. + components: + - type: Sprite + sprite: Objects/Devices/cartridge.rsi + state: cart-psi + - type: Icon + sprite: Objects/Devices/cartridge.rsi + state: cart-psi + - type: UIFragment + ui: !type:PsiWatchUi + - type: Cartridge + programName: psi-watch-program-name + icon: + sprite: DeltaV/Structures/Machines/glimmer_machines.rsi + state: prober + - type: PsiWatchCartridge diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 21d707c915c..61ab7705135 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -369,6 +369,14 @@ - type: Icon sprite: DeltaV/Objects/Devices/pda.rsi # DeltaV - Give Chaplain PDA Epistemics colors state: pda-chaplain + - type: CartridgeLoader # Nyanotrasen - Glimmer Monitor + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - GlimmerMonitorCartridge + - NanoChatCartridge # DeltaV + - PsiWatchCartridge - type: entity name: logistics officer PDA # DeltaV - Logistics Department replacing Cargo @@ -500,6 +508,7 @@ - NewsReaderCartridge - GlimmerMonitorCartridge - NanoChatCartridge + - PsiWatchCartridge - type: entity parent: BasePDA @@ -809,6 +818,7 @@ - NewsReaderCartridge - GlimmerMonitorCartridge - NanoChatCartridge # DeltaV + - PsiWatchCartridge - type: entity parent: BasePDA @@ -832,6 +842,7 @@ - NewsReaderCartridge - GlimmerMonitorCartridge - NanoChatCartridge + - PsiWatchCartridge - type: entity parent: BasePDA @@ -1012,6 +1023,7 @@ - SecWatchCartridge # DeltaV: SecWatch replaces WantedList - StockTradingCartridge # Delta-V - NanoChatCartridge # DeltaV + - PsiWatchCartridge - type: entity parent: CentcomPDA @@ -1387,6 +1399,9 @@ accentVColor: "#8900c9" - type: Icon state: pda-seniorresearcher + - type: CartridgeLoader + preinstalled: + - PsiWatchCartridge - type: entity parent: BaseMedicalPDA diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index c76dba11385..191f7dba941 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -423,6 +423,46 @@ guides: - CriminalRecords +- type: entity + parent: BaseComputerAiAccess + id: ComputerPsionicsRecords + name: psionics registry computer + description: This can be used to check psionics registry records. Only epistemics can modify them. + components: + - type: PsionicsRecordsConsole + - type: UserInterface + interfaces: + enum.PsionicsRecordsConsoleKey.Key: + type: PsionicsRecordsConsoleBoundUserInterface + - type: ActivatableUI + key: enum.PsionicsRecordsConsoleKey.Key + - type: Sprite + layers: + - map: ["computerLayerBody"] + state: computer + - map: ["computerLayerKeyboard"] + state: generic_keyboard + - map: ["computerLayerScreen"] + state: registry + - map: ["computerLayerKeys"] + state: rd_key + - map: [ "enum.WiresVisualLayers.MaintenancePanel" ] + state: generic_panel_open + - type: PointLight + radius: 1.5 + energy: 1.6 + color: "#1f8c28" + - type: Computer + board: PsionicsRecordsComputerCircuitboard + - type: AccessReader + access: + - [ Chapel ] + - [ Library ] + - [ Mantis ] + - [ ResearchDirector ] + # - type: GuideHelp + # guides: # TODO: Add a guide for Psionics Registry + - type: entity parent: BaseComputerAiAccess id: ComputerStationRecords diff --git a/Resources/Prototypes/Loadouts/Jobs/Epistemics/cataloger.yml b/Resources/Prototypes/Loadouts/Jobs/Epistemics/cataloger.yml index 811ae5e2f0b..4eb0ba6973b 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Epistemics/cataloger.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Epistemics/cataloger.yml @@ -60,6 +60,33 @@ - PillCanisterSpaceDrugs # Eyes +- type: loadout + id: LoadoutCatalogerEyesEpiHUD + category: JobsEpistemicsCataloger + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCatalogerEquipment + - !type:CharacterJobRequirement + jobs: + - Librarian + items: + - ClothingEyesHudEpistemics + +- type: loadout + id: LoadoutCatalogerEyesEpiGlasses + category: JobsEpistemicsCataloger + cost: 3 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCatalogerEquipment + - !type:CharacterJobRequirement + jobs: + - Librarian + items: + - ClothingEyesGlassesEpistemics # Gloves diff --git a/Resources/Prototypes/Loadouts/Jobs/Epistemics/chaplain.yml b/Resources/Prototypes/Loadouts/Jobs/Epistemics/chaplain.yml index 970d697014f..7a4527cd7a5 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Epistemics/chaplain.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Epistemics/chaplain.yml @@ -75,6 +75,33 @@ - PillCanisterSpaceDrugs # Eyes +- type: loadout + id: LoadoutChaplainEyesEpiHUD + category: JobsEpistemicsChaplain + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChaplainEquipment + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingEyesHudEpistemics + +- type: loadout + id: LoadoutChaplainEyesEpiGlasses + category: JobsEpistemicsChaplain + cost: 3 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChaplainEquipment + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingEyesGlassesEpistemics # Gloves diff --git a/Resources/Prototypes/Loadouts/Jobs/Epistemics/mystagogue.yml b/Resources/Prototypes/Loadouts/Jobs/Epistemics/mystagogue.yml index f36ca4f6603..eaa7821a17f 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Epistemics/mystagogue.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Epistemics/mystagogue.yml @@ -88,6 +88,33 @@ - PillCanisterSpaceDrugs # Eyes +- type: loadout + id: LoadoutMystagogueEyesEpiHUD + category: JobsEpistemicsMystagogue + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMystagogueEquipment + - !type:CharacterJobRequirement + jobs: + - ResearchDirector + items: + - ClothingEyesHudEpistemics + +- type: loadout + id: LoadoutMystagogueEyesEpiGlasses + category: JobsEpistemicsMystagogue + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMystagogueEquipment + - !type:CharacterJobRequirement + jobs: + - ResearchDirector + items: + - ClothingEyesGlassesEpistemics # Gloves diff --git a/Resources/Prototypes/Loadouts/Jobs/Epistemics/psionicMantis.yml b/Resources/Prototypes/Loadouts/Jobs/Epistemics/psionicMantis.yml index 6df1d8ef1ff..d5f731c9480 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Epistemics/psionicMantis.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Epistemics/psionicMantis.yml @@ -61,6 +61,34 @@ - PillCanisterCryptobiolin # Eyes +- type: loadout + id: LoadoutPsionicMantisEyesEpiHUD + category: JobsEpistemicsPsionicMantis + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutPsionicMantisEquipment + - !type:CharacterJobRequirement + jobs: + - ForensicMantis + items: + - ClothingEyesHudEpistemics + +- type: loadout + id: LoadoutPsionicMantisEyesEpiGlasses + category: JobsEpistemicsPsionicMantis + cost: 3 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutPsionicMantisEquipment + - !type:CharacterJobRequirement + jobs: + - ForensicMantis + items: + - ClothingEyesGlassesEpistemics + # Gloves diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml index f2a16126b96..af0eb5f0f53 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml @@ -122,3 +122,4 @@ - NewsReaderCartridge - GlimmerMonitorCartridge - NanoChatCartridge + - PsiWatchCartridge diff --git a/Resources/Prototypes/StatusIcon/psionics.yml b/Resources/Prototypes/StatusIcon/psionics.yml new file mode 100644 index 00000000000..67aa622204f --- /dev/null +++ b/Resources/Prototypes/StatusIcon/psionics.yml @@ -0,0 +1,27 @@ +- type: psionicsIcon + id: PsionicsIcon + abstract: true + offset: 1 + locationPreference: Left + isShaded: true + +- type: psionicsIcon + parent: PsionicsIcon + id: PsionicsIconSuspected + icon: + sprite: /Textures/Interface/Misc/psionics_icons.rsi + state: hud_suspected + +- type: psionicsIcon + parent: PsionicsIcon + id: PsionicsIconRegistered + icon: + sprite: /Textures/Interface/Misc/psionics_icons.rsi + state: hud_registered + +- type: psionicsIcon + parent: PsionicsIcon + id: PsionicsIconAbusing + icon: + sprite: /Textures/Interface/Misc/psionics_icons.rsi + state: hud_abusing diff --git a/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/equipped-EYES.png new file mode 100644 index 00000000000..1c1f4a830ce Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/icon.png new file mode 100644 index 00000000000..df2d0b77905 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/inhand-left.png b/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/inhand-left.png new file mode 100644 index 00000000000..9a9fd68d551 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/inhand-right.png b/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/inhand-right.png new file mode 100644 index 00000000000..1b4867ad28a Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/meta.json new file mode 100644 index 00000000000..663a6be032d --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Glasses/epiglasses.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Recolored by CerberusWolfie but taken from tgstation at commit https://github.com/tgstation/tgstation/commit/5a73e8f825ff279e82949b9329783a9e3070e2da.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-EYES", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/equipped-EYES.png new file mode 100644 index 00000000000..92d769cebfc Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/icon.png new file mode 100644 index 00000000000..3d0bc5a2140 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/inhand-left.png b/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/inhand-left.png new file mode 100644 index 00000000000..81927119bbf Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/inhand-right.png b/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/inhand-right.png new file mode 100644 index 00000000000..070927cd6e8 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/meta.json new file mode 100644 index 00000000000..920314b00b6 --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Hud/epi.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Recolored by CerberusWolfie but taken from tgstation at commit https://github.com/tgstation/tgstation/commit/5a73e8f825ff279e82949b9329783a9e3070e2da", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-EYES", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Interface/Misc/psionics_icons.rsi/hud_abusing.png b/Resources/Textures/Interface/Misc/psionics_icons.rsi/hud_abusing.png new file mode 100644 index 00000000000..02c090efe36 Binary files /dev/null and b/Resources/Textures/Interface/Misc/psionics_icons.rsi/hud_abusing.png differ diff --git a/Resources/Textures/Interface/Misc/psionics_icons.rsi/hud_registered.png b/Resources/Textures/Interface/Misc/psionics_icons.rsi/hud_registered.png new file mode 100644 index 00000000000..aa8e441c0a4 Binary files /dev/null and b/Resources/Textures/Interface/Misc/psionics_icons.rsi/hud_registered.png differ diff --git a/Resources/Textures/Interface/Misc/psionics_icons.rsi/hud_suspected.png b/Resources/Textures/Interface/Misc/psionics_icons.rsi/hud_suspected.png new file mode 100644 index 00000000000..4c723f8a975 Binary files /dev/null and b/Resources/Textures/Interface/Misc/psionics_icons.rsi/hud_suspected.png differ diff --git a/Resources/Textures/Interface/Misc/psionics_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/psionics_icons.rsi/meta.json new file mode 100644 index 00000000000..556b11590ea --- /dev/null +++ b/Resources/Textures/Interface/Misc/psionics_icons.rsi/meta.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by CerberusWolfie", + "size": { + "x": 8, + "y": 8 + }, + "states": [ + { + "name": "hud_suspected" + }, + { + "name": "hud_registered" + }, + { + "name": "hud_abusing" + } + ] + } diff --git a/Resources/Textures/Objects/Devices/cartridge.rsi/cart-psi.png b/Resources/Textures/Objects/Devices/cartridge.rsi/cart-psi.png new file mode 100644 index 00000000000..446d937c2a4 Binary files /dev/null and b/Resources/Textures/Objects/Devices/cartridge.rsi/cart-psi.png differ diff --git a/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json b/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json index d5b9a8df884..586070e5007 100644 --- a/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json +++ b/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1cdfb0230cc96d0ba751fa002d04f8aa2f25ad7d and tgstation at tgstation at https://github.com/tgstation/tgstation/commit/0c15d9dbcf0f2beb230eba5d9d889ef2d1945bb8, cart-log made by Skarletto (github), cart-sec made by dieselmohawk (discord), cart-nav, cart-med made by ArchRBX (github)", + "copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1cdfb0230cc96d0ba751fa002d04f8aa2f25ad7d and tgstation at tgstation at https://github.com/tgstation/tgstation/commit/0c15d9dbcf0f2beb230eba5d9d889ef2d1945bb8, cart-log made by Skarletto (github), cart-sec made by dieselmohawk (discord), cart-nav, cart-med made by ArchRBX (github), cart-psi made by CerberusWolfie (github/discord)", "size": { "x": 32, "y": 32 @@ -81,6 +81,9 @@ }, { "name": "cart-med" + }, + { + "name": "cart-psi" } ] -} \ No newline at end of file +} diff --git a/Resources/Textures/Structures/Machines/computers.rsi/meta.json b/Resources/Textures/Structures/Machines/computers.rsi/meta.json index 5355f379ab1..9bc48934ae3 100644 --- a/Resources/Textures/Structures/Machines/computers.rsi/meta.json +++ b/Resources/Textures/Structures/Machines/computers.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bd6873fd4dd6a61d7e46f1d75cd4d90f64c40894. comm_syndie made by Veritius, based on comm. generic_panel_open made by Errant, commit https://github.com/space-wizards/space-station-14/pull/32273.", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bd6873fd4dd6a61d7e46f1d75cd4d90f64c40894. comm_syndie made by Veritius, based on comm. generic_panel_open made by Errant, commit https://github.com/space-wizards/space-station-14/pull/32273. registry made by CerberusWolfie, based on explosive.", "size": { "x": 32, "y": 32 @@ -913,6 +913,56 @@ ] ] }, + { + "name": "registry", + "directions": 4, + "delays": [ + [ + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1 + ], + [ + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1 + ], + [ + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1 + ], + [ + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1, + 1, + 0.1, + 0.1 + ] + ] + }, { "name": "forensic", "directions": 4, diff --git a/Resources/Textures/Structures/Machines/computers.rsi/registry.png b/Resources/Textures/Structures/Machines/computers.rsi/registry.png new file mode 100644 index 00000000000..f682c9be743 Binary files /dev/null and b/Resources/Textures/Structures/Machines/computers.rsi/registry.png differ