criminal records revival (#22510)
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
@@ -21,7 +20,6 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _record = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly AccessSystem _access = default!;
|
||||
@@ -85,10 +83,9 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
var targetAccessComponent = EntityManager.GetComponent<AccessComponent>(targetId);
|
||||
|
||||
var jobProto = string.Empty;
|
||||
if (_station.GetOwningStation(uid) is { } station
|
||||
&& EntityManager.TryGetComponent<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|
||||
&& keyStorage.Key != null
|
||||
&& _record.TryGetRecord<GeneralStationRecord>(station, keyStorage.Key.Value, out var record))
|
||||
if (TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|
||||
&& keyStorage.Key is {} key
|
||||
&& _record.TryGetRecord<GeneralStationRecord>(key, out var record))
|
||||
{
|
||||
jobProto = record.JobPrototype;
|
||||
}
|
||||
@@ -103,7 +100,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
possibleAccess,
|
||||
jobProto,
|
||||
privilegedIdName,
|
||||
EntityManager.GetComponent<MetaDataComponent>(targetId).EntityName);
|
||||
Name(targetId));
|
||||
}
|
||||
|
||||
_userInterface.TrySetUiState(uid, IdCardConsoleUiKey.Key, newState);
|
||||
@@ -184,7 +181,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
if (!Resolve(uid, ref component))
|
||||
return true;
|
||||
|
||||
if (!EntityManager.TryGetComponent<AccessReaderComponent>(uid, out var reader))
|
||||
if (!TryComp<AccessReaderComponent>(uid, out var reader))
|
||||
return true;
|
||||
|
||||
var privilegedId = component.PrivilegedIdSlot.Item;
|
||||
@@ -193,10 +190,9 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
|
||||
private void UpdateStationRecord(EntityUid uid, EntityUid targetId, string newFullName, string newJobTitle, JobPrototype? newJobProto)
|
||||
{
|
||||
if (_station.GetOwningStation(uid) is not { } station
|
||||
|| !EntityManager.TryGetComponent<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|
||||
if (!TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|
||||
|| keyStorage.Key is not { } key
|
||||
|| !_record.TryGetRecord<GeneralStationRecord>(station, key, out var record))
|
||||
|| !_record.TryGetRecord<GeneralStationRecord>(key, out var record))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -210,6 +206,6 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
record.JobIcon = newJobProto.Icon;
|
||||
}
|
||||
|
||||
_record.Synchronize(station);
|
||||
_record.Synchronize(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ namespace Content.Server.Administration.Systems
|
||||
if (TryComp(item, out PdaComponent? pda) &&
|
||||
TryComp(pda.ContainedId, out StationRecordKeyStorageComponent? keyStorage) &&
|
||||
keyStorage.Key is { } key &&
|
||||
_stationRecords.TryGetRecord(key.OriginStation, key, out GeneralStationRecord? record))
|
||||
_stationRecords.TryGetRecord(key, out GeneralStationRecord? record))
|
||||
{
|
||||
if (TryComp(entity, out DnaComponent? dna) &&
|
||||
dna.DNA != record.DNA)
|
||||
@@ -363,7 +363,7 @@ namespace Content.Server.Administration.Systems
|
||||
continue;
|
||||
}
|
||||
|
||||
_stationRecords.RemoveRecord(key.OriginStation, key);
|
||||
_stationRecords.RemoveRecord(key);
|
||||
Del(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using Content.Server.CriminalRecords.Systems;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.CriminalRecords.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A component for Criminal Record Console storing an active station record key and a currently applied filter
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(CriminalRecordsConsoleSystem))]
|
||||
public sealed partial class CriminalRecordsConsoleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Currently active station record key.
|
||||
/// There is no station parameter as the console uses the current station.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public uint? ActiveKey;
|
||||
|
||||
/// <summary>
|
||||
/// Currently applied filter.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public StationRecordsFilter? Filter;
|
||||
|
||||
/// <summary>
|
||||
/// Channel to send messages to when someone's status gets changed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<RadioChannelPrototype> SecurityChannel = "Security";
|
||||
|
||||
/// <summary>
|
||||
/// Max length of arrest and crime history strings.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public uint MaxStringLength = 256;
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
using Content.Server.CriminalRecords.Components;
|
||||
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.CriminalRecords;
|
||||
using Content.Shared.Security;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Server.CriminalRecords.Systems;
|
||||
|
||||
public sealed class CriminalRecordsConsoleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AccessReaderSystem _access = default!;
|
||||
[Dependency] private readonly CriminalRecordsSystem _criminalRecords = 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<CriminalRecordsConsoleComponent, RecordModifiedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<CriminalRecordsConsoleComponent, AfterGeneralRecordCreatedEvent>(UpdateUserInterface);
|
||||
|
||||
Subs.BuiEvents<CriminalRecordsConsoleComponent>(CriminalRecordsConsoleKey.Key, subs =>
|
||||
{
|
||||
subs.Event<BoundUIOpenedEvent>(UpdateUserInterface);
|
||||
subs.Event<SelectStationRecord>(OnKeySelected);
|
||||
subs.Event<SetStationRecordFilter>(OnFiltersChanged);
|
||||
subs.Event<CriminalRecordChangeStatus>(OnChangeStatus);
|
||||
subs.Event<CriminalRecordAddHistory>(OnAddHistory);
|
||||
subs.Event<CriminalRecordDeleteHistory>(OnDeleteHistory);
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateUserInterface<T>(Entity<CriminalRecordsConsoleComponent> 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<CriminalRecordsConsoleComponent> 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<CriminalRecordsConsoleComponent> 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<CriminalRecordsConsoleComponent> ent, ref CriminalRecordChangeStatus msg)
|
||||
{
|
||||
// prevent malf client violating wanted/reason nullability
|
||||
if ((msg.Status == SecurityStatus.Wanted) != (msg.Reason != null))
|
||||
return;
|
||||
|
||||
if (!CheckSelected(ent, msg.Session, out var mob, out var key))
|
||||
return;
|
||||
|
||||
if (!_stationRecords.TryGetRecord<CriminalRecord>(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;
|
||||
}
|
||||
|
||||
// when arresting someone add it to history automatically
|
||||
// fallback exists if the player was not set to wanted beforehand
|
||||
if (msg.Status == SecurityStatus.Detained)
|
||||
{
|
||||
var oldReason = record.Reason ?? Loc.GetString("criminal-records-console-unspecified-reason");
|
||||
var history = Loc.GetString("criminal-records-console-auto-history", ("reason", oldReason));
|
||||
_criminalRecords.TryAddHistory(key.Value, history);
|
||||
}
|
||||
|
||||
var oldStatus = record.Status;
|
||||
|
||||
// will probably never fail given the checks above
|
||||
_criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason);
|
||||
|
||||
var name = RecordName(key.Value);
|
||||
var officer = Loc.GetString("criminal-records-console-unknown-officer");
|
||||
if (_idCard.TryFindIdCard(mob.Value, out var id) && id.Comp.FullName is {} fullName)
|
||||
officer = fullName;
|
||||
|
||||
// figure out which radio message to send depending on transition
|
||||
var statusString = (oldStatus, msg.Status) switch
|
||||
{
|
||||
// going from wanted or detained on the spot
|
||||
(_, SecurityStatus.Detained) => "detained",
|
||||
// prisoner did their time
|
||||
(SecurityStatus.Detained, SecurityStatus.None) => "released",
|
||||
// going from wanted to none, must have been a mistake
|
||||
(_, SecurityStatus.None) => "not-wanted",
|
||||
// going from none or detained, AOS or prisonbreak / lazy secoff never set them to released and they reoffended
|
||||
(_, SecurityStatus.Wanted) => "wanted",
|
||||
// this is impossible
|
||||
_ => "not-wanted"
|
||||
};
|
||||
var message = Loc.GetString($"criminal-records-console-{statusString}", ("name", name), ("officer", officer),
|
||||
reason != null ? ("reason", reason) : default!);
|
||||
_radio.SendRadioMessage(ent, message, ent.Comp.SecurityChannel, ent);
|
||||
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
private void OnAddHistory(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordAddHistory msg)
|
||||
{
|
||||
if (!CheckSelected(ent, msg.Session, out _, out var key))
|
||||
return;
|
||||
|
||||
var line = msg.Line.Trim();
|
||||
if (line.Length < 1 || line.Length > ent.Comp.MaxStringLength)
|
||||
return;
|
||||
|
||||
if (!_criminalRecords.TryAddHistory(key.Value, line))
|
||||
return;
|
||||
|
||||
// no radio message since its not crucial to officers patrolling
|
||||
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
private void OnDeleteHistory(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordDeleteHistory msg)
|
||||
{
|
||||
if (!CheckSelected(ent, msg.Session, out _, out var key))
|
||||
return;
|
||||
|
||||
if (!_criminalRecords.TryDeleteHistory(key.Value, msg.Index))
|
||||
return;
|
||||
|
||||
// a bit sus but not crucial to officers patrolling
|
||||
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(Entity<CriminalRecordsConsoleComponent> ent)
|
||||
{
|
||||
var (uid, console) = ent;
|
||||
var owningStation = _station.GetOwningStation(uid);
|
||||
|
||||
if (!TryComp<StationRecordsComponent>(owningStation, out var stationRecords))
|
||||
{
|
||||
_ui.TrySetUiState(uid, CriminalRecordsConsoleKey.Key, new CriminalRecordsConsoleState());
|
||||
return;
|
||||
}
|
||||
|
||||
var listing = _stationRecords.BuildListing((owningStation.Value, stationRecords), console.Filter);
|
||||
|
||||
var state = new CriminalRecordsConsoleState(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.CriminalRecord, stationRecords);
|
||||
state.SelectedKey = id;
|
||||
}
|
||||
|
||||
_ui.TrySetUiState(uid, CriminalRecordsConsoleKey.Key, state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boilerplate that most actions use, if they require that a record be selected.
|
||||
/// Obviously shouldn't be used for selecting records.
|
||||
/// </summary>
|
||||
private bool CheckSelected(Entity<CriminalRecordsConsoleComponent> ent, ICommonSession session,
|
||||
[NotNullWhen(true)] out EntityUid? mob, [NotNullWhen(true)] out StationRecordKey? key)
|
||||
{
|
||||
key = null;
|
||||
mob = null;
|
||||
if (session.AttachedEntity is not {} user)
|
||||
return false;
|
||||
|
||||
if (!_access.IsAllowed(user, ent))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("criminal-records-permission-denied"), ent, session);
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name from a record, or empty string if this somehow fails.
|
||||
/// </summary>
|
||||
private string RecordName(StationRecordKey key)
|
||||
{
|
||||
if (!_stationRecords.TryGetRecord<GeneralStationRecord>(key, out var record))
|
||||
return "";
|
||||
|
||||
return record.Name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.CriminalRecords;
|
||||
using Content.Shared.Security;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.CriminalRecords.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Criminal records
|
||||
///
|
||||
/// Criminal Records inherit Station Records' core and add role-playing tools for Security:
|
||||
/// - Ability to track a person's status (Detained/Wanted/None)
|
||||
/// - See security officers' actions in Criminal Records in the radio
|
||||
/// - See reasons for any action with no need to ask the officer personally
|
||||
/// </summary>
|
||||
public sealed class CriminalRecordsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _stationRecords = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AfterGeneralRecordCreatedEvent>(OnGeneralRecordCreated);
|
||||
}
|
||||
|
||||
private void OnGeneralRecordCreated(AfterGeneralRecordCreatedEvent ev)
|
||||
{
|
||||
_stationRecords.AddRecordEntry(ev.Key, new CriminalRecord());
|
||||
_stationRecords.Synchronize(ev.Key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to change the status of the record found by the StationRecordKey.
|
||||
/// Reason should only be passed if status is Wanted.
|
||||
/// </summary>
|
||||
/// <returns>True if the status is changed, false if not</returns>
|
||||
public bool TryChangeStatus(StationRecordKey key, SecurityStatus status, string? reason)
|
||||
{
|
||||
// don't do anything if its the same status
|
||||
if (!_stationRecords.TryGetRecord<CriminalRecord>(key, out var record)
|
||||
|| status == record.Status)
|
||||
return false;
|
||||
|
||||
record.Status = status;
|
||||
record.Reason = reason;
|
||||
|
||||
_stationRecords.Synchronize(key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a history entry to a criminal record.
|
||||
/// </summary>
|
||||
/// <returns>True if adding succeeded, false if not</returns>
|
||||
public bool TryAddHistory(StationRecordKey key, CrimeHistory entry)
|
||||
{
|
||||
if (!_stationRecords.TryGetRecord<CriminalRecord>(key, out var record))
|
||||
return false;
|
||||
|
||||
record.History.Add(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and tries to add a history entry using the current time.
|
||||
/// </summary>
|
||||
public bool TryAddHistory(StationRecordKey key, string line)
|
||||
{
|
||||
var entry = new CrimeHistory(_timing.CurTime, line);
|
||||
return TryAddHistory(key, entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to delete a sepcific line of history from a criminal record, by index.
|
||||
/// </summary>
|
||||
/// <returns>True if the line was removed, false if not</returns>
|
||||
public bool TryDeleteHistory(StationRecordKey key, uint index)
|
||||
{
|
||||
if (!_stationRecords.TryGetRecord<CriminalRecord>(key, out var record))
|
||||
return false;
|
||||
|
||||
if (index >= record.History.Count)
|
||||
return false;
|
||||
|
||||
record.History.RemoveAt((int) index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -68,18 +68,14 @@ public sealed class RenameCommand : IConsoleCommand
|
||||
// This is done here because ID cards are linked to station records
|
||||
if (_entManager.TrySystem<StationRecordsSystem>(out var recordsSystem)
|
||||
&& _entManager.TryGetComponent(idCard, out StationRecordKeyStorageComponent? keyStorage)
|
||||
&& keyStorage.Key != null)
|
||||
&& keyStorage.Key is {} key)
|
||||
{
|
||||
var origin = keyStorage.Key.Value.OriginStation;
|
||||
|
||||
if (recordsSystem.TryGetRecord<GeneralStationRecord>(origin,
|
||||
keyStorage.Key.Value,
|
||||
out var generalRecord))
|
||||
if (recordsSystem.TryGetRecord<GeneralStationRecord>(key, out var generalRecord))
|
||||
{
|
||||
generalRecord.Name = name;
|
||||
}
|
||||
|
||||
recordsSystem.Synchronize(origin);
|
||||
recordsSystem.Synchronize(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,16 @@ public sealed class ClericalErrorRule : StationEventSystem<ClericalErrorRuleComp
|
||||
var min = (int) Math.Max(1, Math.Round(component.MinToRemove * recordCount));
|
||||
var max = (int) Math.Max(min, Math.Round(component.MaxToRemove * recordCount));
|
||||
var toRemove = RobustRandom.Next(min, max);
|
||||
var keys = new List<StationRecordKey>();
|
||||
var keys = new List<uint>();
|
||||
for (var i = 0; i < toRemove; i++)
|
||||
{
|
||||
keys.Add(RobustRandom.Pick(stationRecords.Records.Keys));
|
||||
}
|
||||
|
||||
foreach (var key in keys)
|
||||
foreach (var id in keys)
|
||||
{
|
||||
_stationRecords.RemoveRecord(chosenStation.Value, key, stationRecords);
|
||||
var key = new StationRecordKey(id, chosenStation.Value);
|
||||
_stationRecords.RemoveRecord(key, stationRecords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Server.StationRecords;
|
||||
namespace Content.Server.StationRecords.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, Access(typeof(GeneralStationRecordConsoleSystem))]
|
||||
public sealed partial class GeneralStationRecordConsoleComponent : Component
|
||||
{
|
||||
public (NetEntity, uint)? ActiveKey { get; set; }
|
||||
public GeneralStationRecordsFilter? Filter { get; set; }
|
||||
/// <summary>
|
||||
/// Selected crewmember record id.
|
||||
/// Station always uses the station that owns the console.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public uint? ActiveKey;
|
||||
|
||||
/// <summary>
|
||||
/// Qualities to filter a search by.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public StationRecordsFilter? Filter;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ using Robust.Shared.Utility;
|
||||
namespace Content.Server.StationRecords;
|
||||
|
||||
/// <summary>
|
||||
/// Set of station records. StationRecordsComponent stores these.
|
||||
/// Keyed by StationRecordKey, which should be obtained from
|
||||
/// Set of station records for a single station. StationRecordsComponent stores these.
|
||||
/// Keyed by the record id, which should be obtained from
|
||||
/// an entity that stores a reference to it.
|
||||
/// A StationRecordKey has both the station entity (use to get the record set) and id (use for this).
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed partial class StationRecordSet
|
||||
@@ -16,22 +17,31 @@ public sealed partial class StationRecordSet
|
||||
[DataField("currentRecordId")]
|
||||
private uint _currentRecordId;
|
||||
|
||||
// TODO add custom type serializer so that keys don't have to be written twice.
|
||||
[DataField("keys")]
|
||||
public HashSet<StationRecordKey> Keys = new();
|
||||
/// <summary>
|
||||
/// Every key id that has a record(s) stored.
|
||||
/// Presumably this is faster than iterating the dictionary to check if any tables have a key.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<uint> Keys = new();
|
||||
|
||||
[DataField("recentlyAccessed")]
|
||||
private HashSet<StationRecordKey> _recentlyAccessed = new();
|
||||
/// <summary>
|
||||
/// Recently accessed key ids which are used to synchronize them efficiently.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
private HashSet<uint> _recentlyAccessed = new();
|
||||
|
||||
[DataField("tables")] // TODO ensure all of this data is serializable.
|
||||
private Dictionary<Type, Dictionary<StationRecordKey, object>> _tables = new();
|
||||
/// <summary>
|
||||
/// Dictionary between a record's type and then each record indexed by id.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
private Dictionary<Type, Dictionary<uint, object>> _tables = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all records of a specific type stored in the record set.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of record to fetch.</typeparam>
|
||||
/// <returns>An enumerable object that contains a pair of both a station key, and the record associated with it.</returns>
|
||||
public IEnumerable<(StationRecordKey, T)> GetRecordsOfType<T>()
|
||||
public IEnumerable<(uint, T)> GetRecordsOfType<T>()
|
||||
{
|
||||
if (!_tables.ContainsKey(typeof(T)))
|
||||
{
|
||||
@@ -52,43 +62,44 @@ public sealed partial class StationRecordSet
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an entry into a record.
|
||||
/// Create a new record with an entry.
|
||||
/// Returns an id that can only be used to access the record for this station.
|
||||
/// </summary>
|
||||
/// <param name="entry">Entry to add.</param>
|
||||
/// <typeparam name="T">Type of the entry that's being added.</typeparam>
|
||||
public StationRecordKey AddRecordEntry<T>(EntityUid station, T entry)
|
||||
public uint? AddRecordEntry<T>(T entry)
|
||||
{
|
||||
if (entry == null)
|
||||
return StationRecordKey.Invalid;
|
||||
return null;
|
||||
|
||||
var key = new StationRecordKey(_currentRecordId++, station);
|
||||
var key = _currentRecordId++;
|
||||
AddRecordEntry(key, entry);
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an entry into a record.
|
||||
/// Add an entry into an existing record.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for the record.</param>
|
||||
/// <param name="key">Key id for the record.</param>
|
||||
/// <param name="entry">Entry to add.</param>
|
||||
/// <typeparam name="T">Type of the entry that's being added.</typeparam>
|
||||
public void AddRecordEntry<T>(StationRecordKey key, T entry)
|
||||
public void AddRecordEntry<T>(uint key, T entry)
|
||||
{
|
||||
if (entry == null)
|
||||
return;
|
||||
|
||||
if (Keys.Add(key))
|
||||
_tables.GetOrNew(typeof(T))[key] = entry;
|
||||
Keys.Add(key);
|
||||
_tables.GetOrNew(typeof(T))[key] = entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get an record entry by type, from this record key.
|
||||
/// </summary>
|
||||
/// <param name="key">The StationRecordKey to get the entries from.</param>
|
||||
/// <param name="key">The record id to get the entries from.</param>
|
||||
/// <param name="entry">The entry that is retrieved from the record set.</param>
|
||||
/// <typeparam name="T">The type of entry to search for.</typeparam>
|
||||
/// <returns>True if the record exists and was retrieved, false otherwise.</returns>
|
||||
public bool TryGetRecordEntry<T>(StationRecordKey key, [NotNullWhen(true)] out T? entry)
|
||||
public bool TryGetRecordEntry<T>(uint key, [NotNullWhen(true)] out T? entry)
|
||||
{
|
||||
entry = default;
|
||||
|
||||
@@ -108,10 +119,10 @@ public sealed partial class StationRecordSet
|
||||
/// <summary>
|
||||
/// Checks if the record associated with this key has an entry of a certain type.
|
||||
/// </summary>
|
||||
/// <param name="key">The record key.</param>
|
||||
/// <param name="key">The record key id.</param>
|
||||
/// <typeparam name="T">Type to check.</typeparam>
|
||||
/// <returns>True if the entry exists, false otherwise.</returns>
|
||||
public bool HasRecordEntry<T>(StationRecordKey key)
|
||||
public bool HasRecordEntry<T>(uint key)
|
||||
{
|
||||
return Keys.Contains(key)
|
||||
&& _tables.TryGetValue(typeof(T), out var table)
|
||||
@@ -122,7 +133,7 @@ public sealed partial class StationRecordSet
|
||||
/// Get the recently accessed keys from this record set.
|
||||
/// </summary>
|
||||
/// <returns>All recently accessed keys from this record set.</returns>
|
||||
public IEnumerable<StationRecordKey> GetRecentlyAccessed()
|
||||
public IEnumerable<uint> GetRecentlyAccessed()
|
||||
{
|
||||
return _recentlyAccessed.ToArray();
|
||||
}
|
||||
@@ -135,17 +146,23 @@ public sealed partial class StationRecordSet
|
||||
_recentlyAccessed.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a recently accessed key from the set.
|
||||
/// </summary>
|
||||
public void RemoveFromRecentlyAccessed(uint key)
|
||||
{
|
||||
_recentlyAccessed.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all record entries related to this key from this set.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to remove.</param>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public bool RemoveAllRecords(StationRecordKey key)
|
||||
public bool RemoveAllRecords(uint key)
|
||||
{
|
||||
if (!Keys.Remove(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var table in _tables.Values)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords.Components;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
@@ -7,126 +8,78 @@ namespace Content.Server.StationRecords.Systems;
|
||||
|
||||
public sealed class GeneralStationRecordConsoleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _stationRecordsSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _stationRecords = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, BoundUIOpenedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, SelectGeneralStationRecord>(OnKeySelected);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, GeneralStationRecordsFilterMsg>(OnFiltersChanged);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, RecordModifiedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, AfterGeneralRecordCreatedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, RecordRemovedEvent>(UpdateUserInterface);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface<T>(EntityUid uid, GeneralStationRecordConsoleComponent component, T ev)
|
||||
{
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnKeySelected(EntityUid uid, GeneralStationRecordConsoleComponent component,
|
||||
SelectGeneralStationRecord msg)
|
||||
{
|
||||
component.ActiveKey = msg.SelectedKey;
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnFiltersChanged(EntityUid uid,
|
||||
GeneralStationRecordConsoleComponent component, GeneralStationRecordsFilterMsg msg)
|
||||
{
|
||||
if (component.Filter == null ||
|
||||
component.Filter.Type != msg.Type || component.Filter.Value != msg.Value)
|
||||
Subs.BuiEvents<GeneralStationRecordConsoleComponent>(GeneralStationRecordConsoleKey.Key, subs =>
|
||||
{
|
||||
component.Filter = new GeneralStationRecordsFilter(msg.Type, msg.Value);
|
||||
UpdateUserInterface(uid, component);
|
||||
subs.Event<BoundUIOpenedEvent>(UpdateUserInterface);
|
||||
subs.Event<SelectStationRecord>(OnKeySelected);
|
||||
subs.Event<SetStationRecordFilter>(OnFiltersChanged);
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateUserInterface<T>(Entity<GeneralStationRecordConsoleComponent> ent, ref T args)
|
||||
{
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
// TODO: instead of copy paste shitcode for each record console, have a shared records console comp they all use
|
||||
// then have this somehow play nicely with creating ui state
|
||||
// if that gets done put it in StationRecordsSystem console helpers section :)
|
||||
private void OnKeySelected(Entity<GeneralStationRecordConsoleComponent> ent, ref SelectStationRecord msg)
|
||||
{
|
||||
ent.Comp.ActiveKey = msg.SelectedKey;
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
private void OnFiltersChanged(Entity<GeneralStationRecordConsoleComponent> 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 UpdateUserInterface(EntityUid uid,
|
||||
GeneralStationRecordConsoleComponent? console = null)
|
||||
private void UpdateUserInterface(Entity<GeneralStationRecordConsoleComponent> ent)
|
||||
{
|
||||
if (!Resolve(uid, ref console))
|
||||
var (uid, console) = ent;
|
||||
var owningStation = _station.GetOwningStation(uid);
|
||||
|
||||
if (!TryComp<StationRecordsComponent>(owningStation, out var stationRecords))
|
||||
{
|
||||
_ui.TrySetUiState(uid, GeneralStationRecordConsoleKey.Key, new GeneralStationRecordConsoleState());
|
||||
return;
|
||||
}
|
||||
|
||||
var owningStation = _stationSystem.GetOwningStation(uid);
|
||||
var listing = _stationRecords.BuildListing((owningStation.Value, stationRecords), console.Filter);
|
||||
|
||||
if (!TryComp<StationRecordsComponent>(owningStation, out var stationRecordsComponent))
|
||||
switch (listing.Count)
|
||||
{
|
||||
GeneralStationRecordConsoleState state = new(null, null, null, null);
|
||||
SetStateForInterface(uid, state);
|
||||
case 0:
|
||||
_ui.TrySetUiState(uid, GeneralStationRecordConsoleKey.Key, new GeneralStationRecordConsoleState());
|
||||
return;
|
||||
case 1:
|
||||
console.ActiveKey = listing.Keys.First();
|
||||
break;
|
||||
}
|
||||
|
||||
if (console.ActiveKey is not { } id)
|
||||
return;
|
||||
}
|
||||
|
||||
var consoleRecords =
|
||||
_stationRecordsSystem.GetRecordsOfType<GeneralStationRecord>(owningStation.Value, stationRecordsComponent);
|
||||
var key = new StationRecordKey(id, owningStation.Value);
|
||||
_stationRecords.TryGetRecord<GeneralStationRecord>(key, out var record, stationRecords);
|
||||
|
||||
var listing = new Dictionary<(NetEntity, uint), string>();
|
||||
|
||||
foreach (var pair in consoleRecords)
|
||||
{
|
||||
if (console.Filter != null && IsSkippedRecord(console.Filter, pair.Item2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
listing.Add(_stationRecordsSystem.Convert(pair.Item1), pair.Item2.Name);
|
||||
}
|
||||
|
||||
if (listing.Count == 0)
|
||||
{
|
||||
GeneralStationRecordConsoleState state = new(null, null, null, console.Filter);
|
||||
SetStateForInterface(uid, state);
|
||||
return;
|
||||
}
|
||||
else if (listing.Count == 1)
|
||||
{
|
||||
console.ActiveKey = listing.Keys.First();
|
||||
}
|
||||
|
||||
GeneralStationRecord? record = null;
|
||||
if (console.ActiveKey != null)
|
||||
{
|
||||
_stationRecordsSystem.TryGetRecord(owningStation.Value, _stationRecordsSystem.Convert(console.ActiveKey.Value), out record,
|
||||
stationRecordsComponent);
|
||||
}
|
||||
|
||||
GeneralStationRecordConsoleState newState = new(console.ActiveKey, record, listing, console.Filter);
|
||||
SetStateForInterface(uid, newState);
|
||||
}
|
||||
|
||||
private void SetStateForInterface(EntityUid uid, GeneralStationRecordConsoleState newState)
|
||||
{
|
||||
_userInterface.TrySetUiState(uid, GeneralStationRecordConsoleKey.Key, newState);
|
||||
}
|
||||
|
||||
private bool IsSkippedRecord(GeneralStationRecordsFilter filter,
|
||||
GeneralStationRecord someRecord)
|
||||
{
|
||||
bool isFilter = filter.Value.Length > 0;
|
||||
string filterLowerCaseValue = "";
|
||||
|
||||
if (!isFilter)
|
||||
return false;
|
||||
|
||||
filterLowerCaseValue = filter.Value.ToLower();
|
||||
|
||||
return filter.Type switch
|
||||
{
|
||||
GeneralStationRecordFilterType.Name =>
|
||||
!someRecord.Name.ToLower().Contains(filterLowerCaseValue),
|
||||
GeneralStationRecordFilterType.Prints => someRecord.Fingerprint != null
|
||||
&& IsFilterWithSomeCodeValue(someRecord.Fingerprint, filterLowerCaseValue),
|
||||
GeneralStationRecordFilterType.DNA => someRecord.DNA != null
|
||||
&& IsFilterWithSomeCodeValue(someRecord.DNA, filterLowerCaseValue),
|
||||
};
|
||||
}
|
||||
|
||||
private bool IsFilterWithSomeCodeValue(string value, string filter)
|
||||
{
|
||||
return !value.ToLower().StartsWith(filter);
|
||||
GeneralStationRecordConsoleState newState = new(id, record, listing, console.Filter);
|
||||
_ui.TrySetUiState(uid, GeneralStationRecordConsoleKey.Key, newState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ namespace Content.Server.StationRecords.Systems;
|
||||
/// </summary>
|
||||
public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
{
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly StationRecordKeyStorageSystem _keyStorageSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly StationRecordKeyStorageSystem _keyStorage = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -45,26 +45,22 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
|
||||
private void OnPlayerSpawn(PlayerSpawnCompleteEvent args)
|
||||
{
|
||||
if (!HasComp<StationRecordsComponent>(args.Station))
|
||||
if (!TryComp<StationRecordsComponent>(args.Station, out var stationRecords))
|
||||
return;
|
||||
|
||||
CreateGeneralRecord(args.Station, args.Mob, args.Profile, args.JobId);
|
||||
CreateGeneralRecord(args.Station, args.Mob, args.Profile, args.JobId, stationRecords);
|
||||
}
|
||||
|
||||
private void CreateGeneralRecord(EntityUid station, EntityUid player, HumanoidCharacterProfile profile,
|
||||
string? jobId, StationRecordsComponent? records = null)
|
||||
string? jobId, StationRecordsComponent records)
|
||||
{
|
||||
if (!Resolve(station, ref records)
|
||||
|| string.IsNullOrEmpty(jobId)
|
||||
// TODO make PlayerSpawnCompleteEvent.JobId a ProtoId
|
||||
if (string.IsNullOrEmpty(jobId)
|
||||
|| !_prototypeManager.HasIndex<JobPrototype>(jobId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_inventorySystem.TryGetSlotEntity(player, "id", out var idUid))
|
||||
{
|
||||
if (!_inventory.TryGetSlotEntity(player, "id", out var idUid))
|
||||
return;
|
||||
}
|
||||
|
||||
TryComp<FingerprintComponent>(player, out var fingerprintComponent);
|
||||
TryComp<DnaComponent>(player, out var dnaComponent);
|
||||
@@ -100,17 +96,28 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
/// Optional - other systems should anticipate this.
|
||||
/// </param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
public void CreateGeneralRecord(EntityUid station, EntityUid? idUid, string name, int age, string species, Gender gender, string jobId, string? mobFingerprint, string? dna, HumanoidCharacterProfile? profile = null,
|
||||
StationRecordsComponent? records = null)
|
||||
public void CreateGeneralRecord(
|
||||
EntityUid station,
|
||||
EntityUid? idUid,
|
||||
string name,
|
||||
int age,
|
||||
string species,
|
||||
Gender gender,
|
||||
string jobId,
|
||||
string? mobFingerprint,
|
||||
string? dna,
|
||||
HumanoidCharacterProfile profile,
|
||||
StationRecordsComponent records)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(jobId, out JobPrototype? jobPrototype))
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<JobPrototype>(jobId, out var jobPrototype))
|
||||
throw new ArgumentException($"Invalid job prototype ID: {jobId}");
|
||||
|
||||
// when adding a record that already exists use the old one
|
||||
// this happens when respawning as the same character
|
||||
if (GetRecordByName(station, name, records) is {} id)
|
||||
{
|
||||
SetIdKey(idUid, new StationRecordKey(id, station));
|
||||
return;
|
||||
}
|
||||
|
||||
var record = new GeneralStationRecord()
|
||||
@@ -129,40 +136,47 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
|
||||
var key = AddRecordEntry(station, record);
|
||||
if (!key.IsValid())
|
||||
return;
|
||||
|
||||
if (idUid != null)
|
||||
{
|
||||
var keyStorageEntity = idUid;
|
||||
if (TryComp(idUid, out PdaComponent? pdaComponent) && pdaComponent.ContainedId != null)
|
||||
{
|
||||
keyStorageEntity = pdaComponent.IdSlot.Item;
|
||||
}
|
||||
|
||||
if (keyStorageEntity != null)
|
||||
{
|
||||
_keyStorageSystem.AssignKey(keyStorageEntity.Value, key);
|
||||
}
|
||||
Log.Warning($"Failed to add general record entry for {name}");
|
||||
return;
|
||||
}
|
||||
|
||||
RaiseLocalEvent(new AfterGeneralRecordCreatedEvent(station, key, record, profile));
|
||||
SetIdKey(idUid, key);
|
||||
|
||||
RaiseLocalEvent(new AfterGeneralRecordCreatedEvent(key, record, profile));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the station records key for an id/pda.
|
||||
/// </summary>
|
||||
public void SetIdKey(EntityUid? uid, StationRecordKey key)
|
||||
{
|
||||
if (uid is not {} idUid)
|
||||
return;
|
||||
|
||||
var keyStorageEntity = idUid;
|
||||
if (TryComp<PdaComponent>(idUid, out var pda) && pda.ContainedId is {} id)
|
||||
{
|
||||
keyStorageEntity = id;
|
||||
}
|
||||
|
||||
_keyStorage.AssignKey(keyStorageEntity, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a record from this station.
|
||||
/// </summary>
|
||||
/// <param name="station">Station to remove the record from.</param>
|
||||
/// <param name="key">The key to remove.</param>
|
||||
/// <param name="key">The station and key to remove.</param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
/// <returns>True if the record was removed, false otherwise.</returns>
|
||||
public bool RemoveRecord(EntityUid station, StationRecordKey key, StationRecordsComponent? records = null)
|
||||
public bool RemoveRecord(StationRecordKey key, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
if (!Resolve(key.OriginStation, ref records))
|
||||
return false;
|
||||
|
||||
if (records.Records.RemoveAllRecords(key))
|
||||
if (records.Records.RemoveAllRecords(key.Id))
|
||||
{
|
||||
RaiseLocalEvent(new RecordRemovedEvent(station, key));
|
||||
RaiseLocalEvent(new RecordRemovedEvent(key));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -174,20 +188,39 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
/// from the provided station record key. Will always return
|
||||
/// null if the key does not match the station.
|
||||
/// </summary>
|
||||
/// <param name="station">Station to get the record from.</param>
|
||||
/// <param name="key">Key to try and index from the record set.</param>
|
||||
/// <param name="key">Station and key to try and index from the record set.</param>
|
||||
/// <param name="entry">The resulting entry.</param>
|
||||
/// <param name="records">Station record component.</param>
|
||||
/// <typeparam name="T">Type to get from the record set.</typeparam>
|
||||
/// <returns>True if the record was obtained, false otherwise.</returns>
|
||||
public bool TryGetRecord<T>(EntityUid station, StationRecordKey key, [NotNullWhen(true)] out T? entry, StationRecordsComponent? records = null)
|
||||
public bool TryGetRecord<T>(StationRecordKey key, [NotNullWhen(true)] out T? entry, StationRecordsComponent? records = null)
|
||||
{
|
||||
entry = default;
|
||||
|
||||
if (!Resolve(station, ref records))
|
||||
if (!Resolve(key.OriginStation, ref records))
|
||||
return false;
|
||||
|
||||
return records.Records.TryGetRecordEntry(key, out entry);
|
||||
return records.Records.TryGetRecordEntry(key.Id, out entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an id if a record with the same name exists.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Linear search so O(n) time complexity.
|
||||
/// </remarks>
|
||||
public uint? GetRecordByName(EntityUid station, string name, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
return null;
|
||||
|
||||
foreach (var (id, record) in GetRecordsOfType<GeneralStationRecord>(station, records))
|
||||
{
|
||||
if (record.Name == name)
|
||||
return id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -197,30 +230,47 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
/// <param name="records">Station records component.</param>
|
||||
/// <typeparam name="T">Type of record to fetch</typeparam>
|
||||
/// <returns>Enumerable of pairs with a station record key, and the entry in question of type T.</returns>
|
||||
public IEnumerable<(StationRecordKey, T)> GetRecordsOfType<T>(EntityUid station, StationRecordsComponent? records = null)
|
||||
public IEnumerable<(uint, T)> GetRecordsOfType<T>(EntityUid station, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
{
|
||||
return Array.Empty<(StationRecordKey, T)>();
|
||||
}
|
||||
return Array.Empty<(uint, T)>();
|
||||
|
||||
return records.Records.GetRecordsOfType<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a record entry to a station's record set.
|
||||
/// Adds a new record entry to a station's record set.
|
||||
/// </summary>
|
||||
/// <param name="station">The station to add the record to.</param>
|
||||
/// <param name="record">The record to add.</param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
/// <typeparam name="T">The type of record to add.</typeparam>
|
||||
public StationRecordKey AddRecordEntry<T>(EntityUid station, T record,
|
||||
StationRecordsComponent? records = null)
|
||||
public StationRecordKey AddRecordEntry<T>(EntityUid station, T record, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
return StationRecordKey.Invalid;
|
||||
|
||||
return records.Records.AddRecordEntry(station, record);
|
||||
var id = records.Records.AddRecordEntry(record);
|
||||
if (id == null)
|
||||
return StationRecordKey.Invalid;
|
||||
|
||||
return new StationRecordKey(id.Value, station);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a record to an existing entry.
|
||||
/// </summary>
|
||||
/// <param name="key">The station and id of the existing entry.</param>
|
||||
/// <param name="record">The record to add.</param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
/// <typeparam name="T">The type of record to add.</typeparam>
|
||||
public void AddRecordEntry<T>(StationRecordKey key, T record,
|
||||
StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(key.OriginStation, ref records))
|
||||
return;
|
||||
|
||||
records.Records.AddRecordEntry(key.Id, record);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -231,17 +281,99 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
public void Synchronize(EntityUid station, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var key in records.Records.GetRecentlyAccessed())
|
||||
{
|
||||
RaiseLocalEvent(new RecordModifiedEvent(station, key));
|
||||
RaiseLocalEvent(new RecordModifiedEvent(new StationRecordKey(key, station)));
|
||||
}
|
||||
|
||||
records.Records.ClearRecentlyAccessed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes a single record's entries for a station.
|
||||
/// </summary>
|
||||
/// <param name="key">The station and id of the record</param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
public void Synchronize(StationRecordKey key, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(key.OriginStation, ref records))
|
||||
return;
|
||||
|
||||
RaiseLocalEvent(new RecordModifiedEvent(key));
|
||||
|
||||
records.Records.RemoveFromRecentlyAccessed(key.Id);
|
||||
}
|
||||
|
||||
#region Console system helpers
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a record should be skipped given a filter.
|
||||
/// Takes general record since even if you are using this for e.g. criminal records,
|
||||
/// you don't want to duplicate basic info like name and dna.
|
||||
/// Station records lets you do this nicely with multiple types having their own data.
|
||||
/// </summary>
|
||||
public bool IsSkipped(StationRecordsFilter? filter, GeneralStationRecord someRecord)
|
||||
{
|
||||
// if nothing is being filtered, show everything
|
||||
if (filter == null)
|
||||
return false;
|
||||
if (filter.Value.Length == 0)
|
||||
return false;
|
||||
|
||||
var filterLowerCaseValue = filter.Value.ToLower();
|
||||
|
||||
return filter.Type switch
|
||||
{
|
||||
StationRecordFilterType.Name =>
|
||||
!someRecord.Name.ToLower().Contains(filterLowerCaseValue),
|
||||
StationRecordFilterType.Prints => someRecord.Fingerprint != null
|
||||
&& IsFilterWithSomeCodeValue(someRecord.Fingerprint, filterLowerCaseValue),
|
||||
StationRecordFilterType.DNA => someRecord.DNA != null
|
||||
&& IsFilterWithSomeCodeValue(someRecord.DNA, filterLowerCaseValue),
|
||||
};
|
||||
}
|
||||
|
||||
private bool IsFilterWithSomeCodeValue(string value, string filter)
|
||||
{
|
||||
return !value.ToLower().StartsWith(filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a record listing of id to name for a station and filter.
|
||||
/// </summary>
|
||||
public Dictionary<uint, string> BuildListing(Entity<StationRecordsComponent> station, StationRecordsFilter? filter)
|
||||
{
|
||||
var listing = new Dictionary<uint, string>();
|
||||
|
||||
var records = GetRecordsOfType<GeneralStationRecord>(station, station.Comp);
|
||||
foreach (var pair in records)
|
||||
{
|
||||
if (IsSkipped(filter, pair.Item2))
|
||||
continue;
|
||||
|
||||
listing.Add(pair.Item1, pair.Item2.Name);
|
||||
}
|
||||
|
||||
return listing;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base event for station record events
|
||||
/// </summary>
|
||||
public abstract class StationRecordEvent : EntityEventArgs
|
||||
{
|
||||
public readonly StationRecordKey Key;
|
||||
public EntityUid Station => Key.OriginStation;
|
||||
|
||||
protected StationRecordEvent(StationRecordKey key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -250,23 +382,19 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
|
||||
/// listening to this event, as it contains the character's record key.
|
||||
/// Also stores the general record reference, to save some time.
|
||||
/// </summary>
|
||||
public sealed class AfterGeneralRecordCreatedEvent : EntityEventArgs
|
||||
public sealed class AfterGeneralRecordCreatedEvent : StationRecordEvent
|
||||
{
|
||||
public readonly EntityUid Station;
|
||||
public StationRecordKey Key { get; }
|
||||
public GeneralStationRecord Record { get; }
|
||||
public readonly GeneralStationRecord Record;
|
||||
/// <summary>
|
||||
/// Profile for the related player. This is so that other systems can get further information
|
||||
/// about the player character.
|
||||
/// Optional - other systems should anticipate this.
|
||||
/// </summary>
|
||||
public HumanoidCharacterProfile? Profile { get; }
|
||||
public readonly HumanoidCharacterProfile Profile;
|
||||
|
||||
public AfterGeneralRecordCreatedEvent(EntityUid station, StationRecordKey key, GeneralStationRecord record,
|
||||
HumanoidCharacterProfile? profile)
|
||||
public AfterGeneralRecordCreatedEvent(StationRecordKey key, GeneralStationRecord record,
|
||||
HumanoidCharacterProfile profile) : base(key)
|
||||
{
|
||||
Station = station;
|
||||
Key = key;
|
||||
Record = record;
|
||||
Profile = profile;
|
||||
}
|
||||
@@ -278,15 +406,10 @@ public sealed class AfterGeneralRecordCreatedEvent : EntityEventArgs
|
||||
/// that store record keys can then remove the key from their internal
|
||||
/// fields.
|
||||
/// </summary>
|
||||
public sealed class RecordRemovedEvent : EntityEventArgs
|
||||
public sealed class RecordRemovedEvent : StationRecordEvent
|
||||
{
|
||||
public readonly EntityUid Station;
|
||||
public StationRecordKey Key { get; }
|
||||
|
||||
public RecordRemovedEvent(EntityUid station, StationRecordKey key)
|
||||
public RecordRemovedEvent(StationRecordKey key) : base(key)
|
||||
{
|
||||
Station = station;
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,14 +418,9 @@ public sealed class RecordRemovedEvent : EntityEventArgs
|
||||
/// inform other systems that records stored in this key
|
||||
/// may have changed.
|
||||
/// </summary>
|
||||
public sealed class RecordModifiedEvent : EntityEventArgs
|
||||
public sealed class RecordModifiedEvent : StationRecordEvent
|
||||
{
|
||||
public readonly EntityUid Station;
|
||||
public StationRecordKey Key { get; }
|
||||
|
||||
public RecordModifiedEvent(EntityUid station, StationRecordKey key)
|
||||
public RecordModifiedEvent(StationRecordKey key) : base(key)
|
||||
{
|
||||
Station = station;
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user