using System.Diagnostics.CodeAnalysis;
using Content.Server.Forensics;
using Content.Server.GameTicking;
using Content.Shared.Inventory;
using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.StationRecords;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Server.StationRecords.Systems;
///
/// Station records.
///
/// A station record is tied to an ID card, or anything that holds
/// a station record's key. This key will determine access to a
/// station record set's record entries, and it is imperative not
/// to lose the item that holds the key under any circumstance.
///
/// Records are mostly a roleplaying tool, but can have some
/// functionality as well (i.e., security records indicating that
/// a specific person holding an ID card with a linked key is
/// currently under warrant, showing a crew manifest with user
/// settable, custom titles).
///
/// General records are tied into this system, as most crewmembers
/// should have a general record - and most systems should probably
/// depend on this general record being created. This is subject
/// to change.
///
public sealed class StationRecordsSystem : SharedStationRecordsSystem
{
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly StationRecordKeyStorageSystem _keyStorageSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnPlayerSpawn);
}
private void OnPlayerSpawn(PlayerSpawnCompleteEvent args)
{
if (!HasComp(args.Station))
return;
CreateGeneralRecord(args.Station, args.Mob, args.Profile, args.JobId);
}
private void CreateGeneralRecord(EntityUid station, EntityUid player, HumanoidCharacterProfile profile,
string? jobId, StationRecordsComponent? records = null)
{
if (!Resolve(station, ref records)
|| string.IsNullOrEmpty(jobId)
|| !_prototypeManager.HasIndex(jobId))
{
return;
}
if (!_inventorySystem.TryGetSlotEntity(player, "id", out var idUid))
{
return;
}
TryComp(player, out var fingerprintComponent);
TryComp(player, out var dnaComponent);
CreateGeneralRecord(station, idUid.Value, profile.Name, profile.ClownName, profile.MimeName, profile.BorgName, profile.Age, profile.Species, profile.Gender, jobId, fingerprintComponent?.Fingerprint, dnaComponent?.DNA, profile, records);
}
///
/// Create a general record to store in a station's record set.
///
///
/// This is tied into the record system, as any crew member's
/// records should generally be dependent on some generic
/// record with the bare minimum of information involved.
///
/// The entity uid of the station.
/// The entity uid of an entity's ID card. Can be null.
/// Name of the character.
/// Species of the character.
/// Gender of the character.
///
/// The job to initially tie this record to. This must be a valid job loaded in, otherwise
/// this call will cause an exception. Ensure that a general record starts out with a job
/// that is currently a valid job prototype.
///
/// Fingerprint of the character.
/// DNA of the character.
///
///
/// 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.
///
/// Station records component.
public void CreateGeneralRecord(EntityUid station, EntityUid? idUid, string name, string clownName, string mimeName, string borgName, int age, string species, Gender gender, string jobId, string? mobFingerprint, string? dna, HumanoidCharacterProfile? profile = null,
StationRecordsComponent? records = null)
{
if (!Resolve(station, ref records))
{
return;
}
if (!_prototypeManager.TryIndex(jobId, out JobPrototype? jobPrototype))
{
throw new ArgumentException($"Invalid job prototype ID: {jobId}");
}
var record = new GeneralStationRecord()
{
Name = name,
ClownName = clownName,
MimeName = mimeName,
BorgName = borgName,
Age = age,
JobTitle = jobPrototype.LocalizedName,
JobIcon = jobPrototype.Icon,
JobPrototype = jobId,
Species = species,
Gender = gender,
DisplayPriority = jobPrototype.Weight,
Fingerprint = mobFingerprint,
DNA = dna,
Profile = profile
};
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);
}
}
RaiseLocalEvent(new AfterGeneralRecordCreatedEvent(station, key, record, profile));
}
///
/// Removes a record from this station.
///
/// Station to remove the record from.
/// The key to remove.
/// Station records component.
/// True if the record was removed, false otherwise.
public bool RemoveRecord(EntityUid station, StationRecordKey key, StationRecordsComponent? records = null)
{
if (!Resolve(station, ref records))
return false;
if (records.Records.RemoveAllRecords(key))
{
RaiseLocalEvent(new RecordRemovedEvent(station, key));
return true;
}
return false;
}
///
/// Try to get a record from this station's record entries,
/// from the provided station record key. Will always return
/// null if the key does not match the station.
///
/// Station to get the record from.
/// Key to try and index from the record set.
/// The resulting entry.
/// Station record component.
/// Type to get from the record set.
/// True if the record was obtained, false otherwise.
public bool TryGetRecord(EntityUid station, StationRecordKey key, [NotNullWhen(true)] out T? entry, StationRecordsComponent? records = null)
{
entry = default;
if (!Resolve(station, ref records))
return false;
return records.Records.TryGetRecordEntry(key, out entry);
}
///
/// Gets all records of a specific type from a station.
///
/// The station to get the records from.
/// Station records component.
/// Type of record to fetch
/// Enumerable of pairs with a station record key, and the entry in question of type T.
public IEnumerable<(StationRecordKey, T)> GetRecordsOfType(EntityUid station, StationRecordsComponent? records = null)
{
if (!Resolve(station, ref records))
{
return Array.Empty<(StationRecordKey, T)>();
}
return records.Records.GetRecordsOfType();
}
///
/// Adds a record entry to a station's record set.
///
/// The station to add the record to.
/// The record to add.
/// Station records component.
/// The type of record to add.
public StationRecordKey AddRecordEntry(EntityUid station, T record,
StationRecordsComponent? records = null)
{
if (!Resolve(station, ref records))
return StationRecordKey.Invalid;
return records.Records.AddRecordEntry(station, record);
}
///
/// Synchronizes a station's records with any systems that need it.
///
/// The station to synchronize any recently accessed records with..
/// Station records component.
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));
}
records.Records.ClearRecentlyAccessed();
}
}
///
/// Event raised after the player's general profile is created.
/// Systems that modify records on a station would have more use
/// listening to this event, as it contains the character's record key.
/// Also stores the general record reference, to save some time.
///
public sealed class AfterGeneralRecordCreatedEvent : EntityEventArgs
{
public readonly EntityUid Station;
public StationRecordKey Key { get; }
public GeneralStationRecord Record { get; }
///
/// 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.
///
public HumanoidCharacterProfile? Profile { get; }
public AfterGeneralRecordCreatedEvent(EntityUid station, StationRecordKey key, GeneralStationRecord record,
HumanoidCharacterProfile? profile)
{
Station = station;
Key = key;
Record = record;
Profile = profile;
}
}
///
/// Event raised after a record is removed. Only the key is given
/// when the record is removed, so that any relevant systems/components
/// that store record keys can then remove the key from their internal
/// fields.
///
public sealed class RecordRemovedEvent : EntityEventArgs
{
public readonly EntityUid Station;
public StationRecordKey Key { get; }
public RecordRemovedEvent(EntityUid station, StationRecordKey key)
{
Station = station;
Key = key;
}
}
///
/// Event raised after a record is modified. This is to
/// inform other systems that records stored in this key
/// may have changed.
///
public sealed class RecordModifiedEvent : EntityEventArgs
{
public readonly EntityUid Station;
public StationRecordKey Key { get; }
public RecordModifiedEvent(EntityUid station, StationRecordKey key)
{
Station = station;
Key = key;
}
}