diff --git a/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml b/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
index 8b68487547f..b558d6114ed 100644
--- a/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
@@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:at="clr-namespace:Content.Client.Administration.UI.Tabs.AdminTab"
+ xmlns:cdAdmin="clr-namespace:Content.Client._CD.Admin.UI"
Margin="4"
MinSize="50 50">
@@ -16,6 +17,8 @@
+
+
diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
index 5bae35da5ba..f0b30601176 100644
--- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
+++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
@@ -1,8 +1,10 @@
+using System.Numerics;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Robust.Client.GameObjects;
+using Robust.Client.Console;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -12,6 +14,8 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly MarkingManager _markingManager = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IClientConsoleHost _consoleHost = default!;
public override void Initialize()
{
@@ -30,6 +34,11 @@ private void UpdateSprite(HumanoidAppearanceComponent component, SpriteComponent
UpdateLayers(component, sprite);
ApplyMarkingSet(component, sprite);
+ var speciesPrototype = _prototypeManager.Index(component.Species);
+ var height = Math.Clamp(MathF.Round(component.Height, 1), speciesPrototype.MinHeight, speciesPrototype.MaxHeight); // should NOT be locked, at all
+
+ sprite.Scale = new Vector2(speciesPrototype.ScaleHeight ? height : 1f, height);
+
sprite[sprite.LayerMapReserveBlank(HumanoidVisualLayers.Eyes)].Color = component.EyeColor;
}
@@ -194,6 +203,7 @@ public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile
humanoid.Species = profile.Species;
humanoid.SkinColor = profile.Appearance.SkinColor;
humanoid.EyeColor = profile.Appearance.EyeColor;
+ humanoid.Height = profile.Height;
UpdateSprite(humanoid, Comp(uid));
}
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
index d0dd02a58ad..15ce473c6ff 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
@@ -67,6 +67,14 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
index 3e2e379328d..0afdc6ef991 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
@@ -1,3 +1,4 @@
+using System.Globalization;
using System.Linq;
using System.Numerics;
using Content.Client.Guidebook;
@@ -36,6 +37,10 @@
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Direction = Robust.Shared.Maths.Direction;
+// CD: Records editor imports
+using Content.Client._CD.Records.UI;
+using Content.Shared._CD.Records;
+
namespace Content.Client.Preferences.UI
{
public sealed class HighlightedContainer : PanelContainer
@@ -78,6 +83,7 @@ public sealed partial class HumanoidProfileEditor : Control
private SingleMarkingPicker _hairPicker => CHairStylePicker;
private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
private EyeColorPicker _eyesPicker => CEyeColorPicker;
+ private LineEdit _heightPicker => CHeight;
private TabContainer _tabContainer => CTabContainer;
private BoxContainer _jobList => CJobList;
@@ -108,6 +114,11 @@ public sealed partial class HumanoidProfileEditor : Control
public event Action? OnProfileChanged;
+ private float _defaultHeight = 1f;
+
+ // CD: Record editor
+ private readonly RecordEditorGui _recordsTab;
+
public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager,
IEntityManager entityManager, IConfigurationManager configurationManager)
{
@@ -196,6 +207,41 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
#endregion Species
+ #region CDHeight
+
+ _heightPicker.OnTextChanged += args =>
+ {
+ if (Profile is null || !float.TryParse(args.Text, out var newHeight))
+ return;
+
+ var prototype = _prototypeManager.Index(Profile.Species);
+ newHeight = MathF.Round(Math.Clamp(newHeight, prototype.MinHeight, prototype.MaxHeight), 2);
+
+ // The percentage between the start and end numbers, aka "inverse lerp"
+ var sliderPercent = (newHeight - prototype.MinHeight) /
+ (prototype.MaxHeight - prototype.MinHeight);
+ CDHeightSlider.Value = sliderPercent;
+
+ SetProfileHeight(newHeight);
+ };
+
+ CHeightReset.OnPressed += _ =>
+ {
+ _heightPicker.SetText(_defaultHeight.ToString(CultureInfo.InvariantCulture), true);
+ };
+
+ CDHeightSlider.OnValueChanged += _ =>
+ {
+ if (Profile is null)
+ return;
+ var prototype = _prototypeManager.Index(Profile.Species);
+ var newHeight = MathF.Round(MathHelper.Lerp(prototype.MinHeight, prototype.MaxHeight, CDHeightSlider.Value), 2);
+ _heightPicker.Text = newHeight.ToString(CultureInfo.InvariantCulture);
+ SetProfileHeight(newHeight);
+ };
+
+ #endregion CDHeight
+
#region Skin
@@ -481,6 +527,14 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
#endregion Markings
+ #region CosmaticRecords
+
+ _recordsTab = new RecordEditorGui(UpdateProfileRecords);
+ _tabContainer.AddChild(_recordsTab);
+ _tabContainer.SetTabTitle(_tabContainer.ChildCount - 1, Loc.GetString("humanoid-profile-editor-cd-records-tab"));
+
+ #endregion CosmaticRecords
+
#region FlavorText
if (_configurationManager.GetCVar(CCVars.FlavorText))
@@ -658,6 +712,15 @@ private void UpdateRoleRequirements()
}
}
+ // CD: Records editor
+ private void UpdateProfileRecords(CharacterRecords records)
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCDCharacterRecords(records);
+ IsDirty = true;
+ }
+
///
/// DeltaV - Make sure that no invalid job priorities get through.
///
@@ -869,6 +932,12 @@ private void SetBackpack(BackpackPreference newBackpack)
IsDirty = true;
}
+ private void SetProfileHeight(float height)
+ {
+ Profile = Profile?.WithHeight(height);
+ IsDirty = true;
+ }
+
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
{
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
@@ -1069,6 +1138,24 @@ private void UpdateBackpackControls()
_backpackButton.SelectId((int) Profile.Backpack);
}
+ private void UpdateHeightControls()
+ {
+ if (Profile == null)
+ {
+ return;
+ }
+
+ var species = _speciesList.Find(x => x.ID == Profile.Species);
+ if (species != null)
+ _defaultHeight = species.DefaultHeight;
+
+ var prototype = _prototypeManager.Index(Profile.Species);
+ var sliderPercent = (Profile.Height - prototype.MinHeight) /
+ (prototype.MaxHeight - prototype.MinHeight);
+ CDHeightSlider.Value = sliderPercent;
+ _heightPicker.Text = Profile.Height.ToString(CultureInfo.InvariantCulture);
+ }
+
private void UpdateSpawnPriorityControls()
{
if (Profile == null)
@@ -1230,6 +1317,10 @@ public void UpdateControls()
UpdateHairPickers();
UpdateCMarkingsHair();
UpdateCMarkingsFacialHair();
+ UpdateHeightControls();
+
+ // CD: Update record editor
+ _recordsTab.Update(Profile);
_preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
}
diff --git a/Content.Client/_CD/Admin/UI/ModifyCharacterRecords.xaml b/Content.Client/_CD/Admin/UI/ModifyCharacterRecords.xaml
new file mode 100644
index 00000000000..9cb31849704
--- /dev/null
+++ b/Content.Client/_CD/Admin/UI/ModifyCharacterRecords.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_CD/Admin/UI/ModifyCharacterRecords.xaml.cs b/Content.Client/_CD/Admin/UI/ModifyCharacterRecords.xaml.cs
new file mode 100644
index 00000000000..9a868650997
--- /dev/null
+++ b/Content.Client/_CD/Admin/UI/ModifyCharacterRecords.xaml.cs
@@ -0,0 +1,48 @@
+using Content.Shared._CD.Records;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._CD.Admin.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class ModifyCharacterRecords : DefaultWindow
+{
+ public ModifyCharacterRecords()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ foreach (var v in Enum.GetValues())
+ {
+ EntityEntryType.AddItem(v.ToString());
+ }
+
+ EntityEntryType.OnItemSelected += args =>
+ {
+ EntityEntryType.SelectId(args.Id);
+ UpdateCommands();
+ };
+
+ EntityEdit.OnTextChanged += _ => UpdateCommands();
+ EntityEntryIndex.OnTextChanged += _ => UpdateCommands();
+ }
+
+ private void UpdateCommands()
+ {
+ if (!int.TryParse(EntityEdit.Text, out var uid))
+ {
+ return;
+ }
+
+ if (!int.TryParse(EntityEntryIndex.Text, out var idx))
+ {
+ return;
+ }
+
+ var ty = (CharacterRecordType)EntityEntryType.SelectedId;
+
+ PurgeCommand.Command = $"purgecharacterrecords {uid}";
+ DelCommand.Command = $"delrecordentry {uid} {ty.ToString()} {idx}";
+ }
+}
diff --git a/Content.Client/_CD/Records/UI/CharacterRecordConsoleBoundUserInterface.cs b/Content.Client/_CD/Records/UI/CharacterRecordConsoleBoundUserInterface.cs
new file mode 100644
index 00000000000..c1da4bdb331
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/CharacterRecordConsoleBoundUserInterface.cs
@@ -0,0 +1,82 @@
+using Content.Shared._CD.Records;
+using Content.Shared.CriminalRecords;
+using Content.Shared.CriminalRecords.Components;
+using Content.Shared.Security;
+using Content.Shared.StationRecords;
+using JetBrains.Annotations;
+
+namespace Content.Client._CD.Records.UI;
+
+[UsedImplicitly]
+public sealed class CharacterRecordConsoleBoundUserInterface : BoundUserInterface
+{
+ [ViewVariables] private CharacterRecordViewer? _window;
+
+ public CharacterRecordConsoleBoundUserInterface(EntityUid owner, Enum key)
+ : base(owner, key)
+ {
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState baseState)
+ {
+ base.UpdateState(baseState);
+ if (baseState is not CharacterRecordConsoleState state)
+ return;
+
+ if (_window?.IsSecurity() ?? false)
+ {
+ var comp = EntMan.GetComponent(Owner);
+ _window!.SecurityWantedStatusMaxLength = comp.MaxStringLength;
+ }
+
+ _window?.UpdateState(state);
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _window = new();
+ _window.OnClose += Close;
+ _window.OnListingItemSelected += (ent, stationRecordKey) =>
+ {
+ SendMessage(new CharacterRecordConsoleSelectMsg(ent));
+
+ // If we are a security records console, we also need to inform the criminal records
+ // system of our state.
+ if (_window.IsSecurity() && stationRecordKey != null)
+ {
+ SendMessage(new SelectStationRecord(stationRecordKey));
+ _window.SetSecurityStatusEnabled(true);
+ }
+ else
+ {
+ // If the user does not have criminal records for some reason, we should not be able
+ // to set their wanted status
+ _window.SetSecurityStatusEnabled(false);
+ }
+ };
+
+ _window.OnFiltersChanged += (ty, txt) =>
+ {
+ if (txt == null)
+ SendMessage(new CharacterRecordsConsoleFilterMsg(null));
+ else
+ SendMessage(new CharacterRecordsConsoleFilterMsg(new StationRecordsFilter(ty, txt)));
+ };
+
+ _window.OnSetSecurityStatus += (status, reason) =>
+ {
+ SendMessage(new CriminalRecordChangeStatus(status, reason));
+ };
+
+ _window.OpenCentered();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ _window?.Close();
+ }
+}
diff --git a/Content.Client/_CD/Records/UI/CharacterRecordViewer.xaml b/Content.Client/_CD/Records/UI/CharacterRecordViewer.xaml
new file mode 100644
index 00000000000..5728543d00e
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/CharacterRecordViewer.xaml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_CD/Records/UI/CharacterRecordViewer.xaml.cs b/Content.Client/_CD/Records/UI/CharacterRecordViewer.xaml.cs
new file mode 100644
index 00000000000..bba1390849b
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/CharacterRecordViewer.xaml.cs
@@ -0,0 +1,361 @@
+using System.Linq;
+using Content.Client.UserInterface.Controls;
+using Content.Shared._CD.Records;
+using Content.Shared.Administration;
+using Content.Shared.Security;
+using Content.Shared.StationRecords;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._CD.Records.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class CharacterRecordViewer : FancyWindow
+{
+ public event Action? OnListingItemSelected;
+ public event Action? OnFiltersChanged;
+
+ private bool _isPopulating;
+ private StationRecordFilterType _filterType;
+
+ private RecordConsoleType? _type;
+
+ private readonly RecordEntryViewPopup _entryView = new();
+ private List? _entries;
+
+ private DialogWindow? _wantedReasonDialog;
+ public event Action? OnSetSecurityStatus;
+
+ public uint? SecurityWantedStatusMaxLength;
+
+ public CharacterRecordViewer()
+ {
+ RobustXamlLoader.Load(this);
+
+ // There is no reason why we can't just steal the StationRecordFilter class.
+ // If wizden adds a new kind of filtering we want to replicate it here.
+ foreach (var item in Enum.GetValues())
+ {
+ RecordFilterType.AddItem(GetTypeFilterLocals(item), (int)item);
+ }
+
+ // Again, if wizden changes something about Criminal Records, we want to replicate the
+ // functionality here.
+ foreach (var status in Enum.GetValues())
+ {
+ var name = Loc.GetString($"criminal-records-status-{status.ToString().ToLower()}");
+ StatusOptionButton.AddItem(name, (int)status);
+ }
+
+ RecordListing.OnItemSelected += _ =>
+ {
+ if (!RecordListing.GetSelected().Any())
+ return;
+ var selected = RecordListing.GetSelected().First();
+ var (index, listingKey) = ((uint, uint?))selected.Metadata!;
+ OnListingItemSelected?.Invoke(index, listingKey);
+ };
+
+ RecordListing.OnItemDeselected += _ =>
+ {
+ // When we populate the records, we clear the contents of the listing.
+ // This could cause a deselection but we don't want to really deselect because it would
+ // interrupt what the player is doing.
+ if (!_isPopulating)
+ OnListingItemSelected?.Invoke(null, null);
+ };
+
+ RecordFilters.OnPressed += _ =>
+ {
+ OnFiltersChanged?.Invoke(_filterType, RecordFiltersValue.Text);
+ };
+
+ RecordFiltersReset.OnPressed += _ =>
+ {
+ OnFiltersChanged?.Invoke(StationRecordFilterType.Name, null);
+ RecordFiltersValue.Clear();
+ };
+
+ RecordFiltersValue.OnTextEntered += text =>
+ {
+ OnFiltersChanged?.Invoke(_filterType, text.Text);
+ };
+
+ RecordFilterType.OnItemSelected += eventArgs =>
+ {
+ var type = (StationRecordFilterType)eventArgs.Id;
+ _filterType = type;
+ RecordFilterType.SelectId(eventArgs.Id);
+ };
+
+ RecordEntryViewButton.OnPressed += _ =>
+ {
+ if (_entries == null || !RecordEntryList.GetSelected().Any())
+ return;
+ int idx = RecordEntryList.IndexOf(RecordEntryList.GetSelected().First());
+ _entryView.SetContents(_entries[idx]);
+ _entryView.Open();
+ };
+
+ StatusOptionButton.OnItemSelected += args =>
+ {
+ var status = (SecurityStatus)args.Id;
+ // This should reflect SetStatus in CriminalRecordsConsoleWindow.xaml.cs
+ if (status == SecurityStatus.Wanted || status == SecurityStatus.Suspected)
+ SetStatusWithReason(status);
+ else
+ OnSetSecurityStatus?.Invoke(status, null);
+ };
+
+ OnClose += () => _entryView.Close();
+
+ // Admin console entry type selector
+ RecordEntryViewType.AddItem(Loc.GetString("department-Security"));
+ RecordEntryViewType.AddItem(Loc.GetString("department-Medical"));
+ RecordEntryViewType.AddItem(Loc.GetString("humanoid-profile-editor-cd-records-employment"));
+ RecordEntryViewType.OnItemSelected += args =>
+ {
+ RecordEntryViewType.SelectId(args.Id);
+ // This is a hack to get the server to send us another packet with the new entries
+ OnFiltersChanged?.Invoke(_filterType, RecordFiltersValue.Text);
+ };
+ }
+
+ // If we are using wizden's class we might as well use their localization.
+ private string GetTypeFilterLocals(StationRecordFilterType type)
+ {
+ return Loc.GetString($"general-station-record-{type.ToString().ToLower()}-filter");
+ }
+
+ public void UpdateState(CharacterRecordConsoleState state)
+ {
+ #region Visibility
+
+ RecordEntryViewType.Visible = false;
+ _type = state.ConsoleType;
+
+ // Disable listing if we don't have one selected
+ if (state.RecordListing == null)
+ {
+ RecordListingStatus.Visible = true;
+ RecordListing.Visible = false;
+ RecordListingStatus.Text = Loc.GetString("cd-record-viewer-empty-state");
+ RecordContainer.Visible = false;
+ RecordContainerStatus.Visible = false;
+ return;
+ }
+
+ RecordListingStatus.Visible = false;
+ RecordListing.Visible = true;
+
+ // Enable extended filtering only for admin and security consoles
+ switch (_type)
+ {
+ case RecordConsoleType.Employment:
+ RecordFilterType.Visible = false;
+ RecordFilterType.SelectId((int)StationRecordFilterType.Name);
+
+ Title = Loc.GetString("cd-character-records-viewer-title-employ");
+ break;
+ case RecordConsoleType.Medical:
+ RecordFilterType.Visible = false;
+ RecordFilterType.SelectId((int)StationRecordFilterType.Name);
+
+ Title = Loc.GetString("cd-character-records-viewer-title-med");
+ break;
+ case RecordConsoleType.Security:
+ RecordFilterType.Visible = true;
+
+ Title = Loc.GetString("cd-character-records-viewer-title-sec");
+ break;
+ case RecordConsoleType.Admin:
+ RecordFilterType.Visible = true;
+ Title = "Admin records console";
+ RecordEntryViewType.Visible = true;
+
+ break;
+ }
+
+ #endregion
+
+ #region PopulateListing
+
+ if (state.Filter != null)
+ {
+ RecordFiltersValue.SetText(state.Filter.Value);
+ RecordFilterType.SelectId((int) state.Filter.Type);
+ }
+
+ // If the counts are the same it is probably not needed to refresh the entry list. This provides
+ // a much better UI experience at the cost of the user possibly needing to re-open the UI under
+ // very specific circumstances that are *very* unlikely to appear in real gameplay.
+ if (RecordListing.Count != state.RecordListing.Count)
+ {
+ _isPopulating = true;
+
+ RecordListing.Clear();
+ foreach (var (key, (txt, stationRecordsKey)) in state.RecordListing)
+ {
+ RecordListing.AddItem(txt, metadata: (key, stationRecordsKey));
+ }
+
+ _isPopulating = false;
+ }
+
+ #endregion
+
+ #region FillRecordContainer
+
+ // Enable container if we have a record selected
+ if (state.SelectedRecord == null)
+ {
+ RecordContainerStatus.Visible = true;
+ RecordContainer.Visible = false;
+ return;
+ }
+
+ RecordContainerStatus.Visible = false;
+ RecordContainer.Visible = true;
+
+ var record = state.SelectedRecord!;
+ var cr = record.CharacterRecords;
+
+ // Basic info
+ RecordContainerName.Text = record.Name;
+ RecordContainerAge.Text = record.Age.ToString();
+ RecordContainerJob.Text = record.JobTitle; /* At some point in the future we might want to display the icon */
+ RecordContainerGender.Text = record.Gender.ToString();
+ RecordContainerSpecies.Text = record.Species;
+ RecordContainerHeight.Text = cr.Height + " " + UnitConversion.GetImperialDisplayLength(cr.Height);
+ RecordContainerWeight.Text = cr.Weight + " " + UnitConversion.GetImperialDisplayMass(cr.Weight);
+ RecordContainerContactName.SetValue(cr.EmergencyContactName);
+
+ RecordContainerEmployment.Visible = false;
+ RecordContainerMedical.Visible = false;
+ RecordContainerSecurity.Visible = false;
+
+ switch (_type)
+ {
+ case RecordConsoleType.Employment:
+ SetEntries(cr.EmploymentEntries);
+ UpdateRecordBoxEmployment(record);
+ break;
+ case RecordConsoleType.Medical:
+ SetEntries(cr.MedicalEntries);
+ UpdateRecordBoxMedical(record);
+ break;
+ case RecordConsoleType.Security:
+ SetEntries(cr.SecurityEntries);
+ UpdateRecordBoxSecurity(record, state.SelectedSecurityStatus);
+ break;
+ case RecordConsoleType.Admin:
+ UpdateRecordBoxEmployment(record);
+ UpdateRecordBoxMedical(record);
+ UpdateRecordBoxSecurity(record, state.SelectedSecurityStatus);
+ switch ((RecordConsoleType) RecordEntryViewType.SelectedId)
+ {
+ case RecordConsoleType.Employment:
+ SetEntries(cr.EmploymentEntries, true);
+ break;
+ case RecordConsoleType.Medical:
+ SetEntries(cr.MedicalEntries, true);
+ break;
+ case RecordConsoleType.Security:
+ SetEntries(cr.SecurityEntries, true);
+ break;
+ }
+ break;
+ }
+
+ #endregion
+
+ }
+
+ private void SetEntries(List entries, bool addIndex = false)
+ {
+ _entries = entries;
+ RecordEntryList.Clear();
+ var i = 0;
+ foreach (var entry in entries)
+ {
+ RecordEntryList.AddItem(addIndex ? $"({i.ToString()}) " + entry.Title : entry.Title);
+ ++i;
+ }
+ }
+
+ private void UpdateRecordBoxEmployment(FullCharacterRecords record)
+ {
+ RecordContainerEmployment.Visible = true;
+ RecordContainerWorkAuth.Text = record.CharacterRecords.HasWorkAuthorization ? "yes" : "no";
+ }
+
+ private void UpdateRecordBoxMedical(FullCharacterRecords record)
+ {
+ RecordContainerMedical.Visible = true;
+ var cr = record.CharacterRecords;
+ RecordContainerMedical.Visible = true;
+ RecordContainerAllergies.SetValue(cr.Allergies);
+ RecordContainerDrugAllergies.SetValue(cr.DrugAllergies);
+ RecordContainerPostmortem.SetValue(cr.PostmortemInstructions);
+ RecordContainerSex.Text = record.Sex.ToString();
+ }
+
+ private void UpdateRecordBoxSecurity(FullCharacterRecords record, (SecurityStatus, string?)? criminal)
+ {
+ RecordContainerSecurity.Visible = true;
+ RecordContainerIdentFeatures.SetValue(record.CharacterRecords.IdentifyingFeatures);
+ RecordContainerFingerprint.Text = record.Fingerprint ?? Loc.GetString("cd-character-records-viewer-unknown");
+ RecordContainerDNA.Text = record.DNA ?? Loc.GetString("cd-character-records-viewer-unknown");
+
+ RecordContainerWantedReason.Visible = false;
+ if (criminal != null)
+ {
+ var (stat, reason) = criminal.Value;
+ StatusOptionButton.Select((int)stat);
+ RecordContainerWantedReason.Text = reason;
+ RecordContainerWantedReason.Visible = reason != null;
+ }
+ }
+
+ // This is copied almost verbatim from CriminalRecordsConsoleWindow.xaml.cs
+ private void SetStatusWithReason(SecurityStatus status)
+ {
+ if (_wantedReasonDialog != null)
+ {
+ _wantedReasonDialog.MoveToFront();
+ return;
+ }
+
+ const string field = "reason";
+ var title = Loc.GetString("criminal-records-status-" + status.ToString().ToLower());
+ var placeholder = Loc.GetString("cd-character-records-viewer-setwanted-placeholder");
+ var prompt = Loc.GetString("criminal-records-console-reason");
+ var entry = new QuickDialogEntry(field, QuickDialogEntryType.LongText, prompt, placeholder);
+ var entries = new List() { entry };
+ _wantedReasonDialog = new DialogWindow(title, entries);
+
+ _wantedReasonDialog.OnConfirmed += responses =>
+ {
+ var reason = responses[field];
+ if (reason.Length < 1 || reason.Length > SecurityWantedStatusMaxLength)
+ return;
+
+ OnSetSecurityStatus?.Invoke(status, reason);
+ };
+
+ _wantedReasonDialog.OnClose += () => { _wantedReasonDialog = null; };
+ }
+ public bool IsSecurity()
+ {
+ return _type == RecordConsoleType.Security || _type == RecordConsoleType.Admin;
+ }
+
+ public void SetSecurityStatusEnabled(bool setting)
+ {
+ for (var i = 0; i < StatusOptionButton.ItemCount; ++i)
+ {
+ StatusOptionButton.SetItemDisabled(i, !setting);
+ }
+ }
+}
+
diff --git a/Content.Client/_CD/Records/UI/RecordEditorEntrySelector.xaml b/Content.Client/_CD/Records/UI/RecordEditorEntrySelector.xaml
new file mode 100644
index 00000000000..328e36d0b86
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/RecordEditorEntrySelector.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_CD/Records/UI/RecordEditorEntrySelector.xaml.cs b/Content.Client/_CD/Records/UI/RecordEditorEntrySelector.xaml.cs
new file mode 100644
index 00000000000..61d569625b4
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/RecordEditorEntrySelector.xaml.cs
@@ -0,0 +1,136 @@
+using System.Linq;
+using Content.Shared._CD.Records;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._CD.Records.UI;
+
+///
+/// The box that contains the list of entities in the record editor. We create one for each record type
+///
+[GenerateTypedNameReferences]
+public sealed partial class RecordEditorEntrySelector : Control
+{
+ private List _entries = new();
+
+ public event Action? OnUpdateEntries;
+
+ private readonly RecordEntryEditPopup _editPopup = new();
+ private readonly RecordEntryViewPopup _entryViewPopup = new();
+ private int _editIdx;
+
+ public RecordEditorEntrySelector()
+ {
+ RobustXamlLoader.Load(this);
+
+ AddButton.OnPressed += _ =>
+ {
+ _editIdx = _entries.Count;
+ _editPopup.SetContents(new CharacterRecords.RecordEntry("", "", ""));
+ _editPopup.Open();
+ };
+
+ EditButton.OnPressed += _ =>
+ {
+ if (!EntrySelector.GetSelected().Any())
+ return;
+ _editIdx = EntrySelector.IndexOf(EntrySelector.GetSelected().First());
+ _editPopup.SetContents(_entries[_editIdx]);
+ _editPopup.Open();
+ };
+
+ ViewButton.OnPressed += _ =>
+ {
+ if (!EntrySelector.GetSelected().Any())
+ return;
+ var idx = EntrySelector.IndexOf(EntrySelector.GetSelected().First());
+ _entryViewPopup.SetContents(_entries[idx]);
+ _entryViewPopup.Open();
+ };
+
+ RemoveButton.OnPressed += _ =>
+ {
+ if (!EntrySelector.GetSelected().Any())
+ return;
+ // Remove the entry, being careful to set the index correctly
+ int idx = EntrySelector.IndexOf(EntrySelector.GetSelected().First());
+ EntrySelector.RemoveAt(idx);
+ _entries.RemoveAt(idx);
+ if (idx == _editIdx)
+ _editPopup.Close();
+ _editIdx--;
+ OnUpdateEntries?.Invoke(new RecordEditorEntryUpdateArgs(_entries));
+ };
+
+ UpButton.OnPressed += _ =>
+ {
+ if (!EntrySelector.GetSelected().Any())
+ return;
+ int idx = EntrySelector.IndexOf(EntrySelector.GetSelected().First());
+ if (idx < 1)
+ return;
+ (_entries[idx], _entries[idx - 1]) = (_entries[idx - 1], _entries[idx]);
+ (EntrySelector[idx], EntrySelector[idx - 1]) = (EntrySelector[idx - 1], EntrySelector[idx]);
+ OnUpdateEntries?.Invoke(new RecordEditorEntryUpdateArgs(_entries));
+ };
+
+ DownButton.OnPressed += _ =>
+ {
+ if (!EntrySelector.GetSelected().Any())
+ return;
+ int idx = EntrySelector.IndexOf(EntrySelector.GetSelected().First());
+ if (idx >= EntrySelector.Count - 1)
+ return;
+ (_entries[idx], _entries[idx + 1]) = (_entries[idx + 1], _entries[idx]);
+ (EntrySelector[idx], EntrySelector[idx + 1]) = (EntrySelector[idx + 1], EntrySelector[idx]);
+ OnUpdateEntries?.Invoke(new RecordEditorEntryUpdateArgs(_entries));
+ };
+
+ _editPopup.SaveButton.OnPressed += _ =>
+ {
+ if (_editIdx >= _entries.Count)
+ {
+ var rec = _editPopup.GetContents();
+ _entries.Add(rec);
+ EntrySelector.AddItem(rec.Title);
+ }
+ else
+ {
+ _entries[_editIdx] = _editPopup.GetContents();
+ EntrySelector[_editIdx].Text = _entries[_editIdx].Title;
+ }
+ OnUpdateEntries?.Invoke(new RecordEditorEntryUpdateArgs(_entries));
+ };
+ OnVisibilityChanged += _ =>
+ {
+ _editPopup.Close();
+ };
+ }
+
+ public void UpdateContents(List entries)
+ {
+ _entries = entries;
+ RefreshSelector();
+ }
+
+ private void RefreshSelector()
+ {
+ EntrySelector.Clear();
+ foreach (var entry in _entries)
+ {
+ EntrySelector.AddItem(entry.Title);
+ }
+ }
+
+ public sealed class RecordEditorEntryUpdateArgs
+ {
+ public List Entries { get; private set; }
+
+ public RecordEditorEntryUpdateArgs(List entries)
+ {
+ Entries = entries;
+ }
+ }
+}
diff --git a/Content.Client/_CD/Records/UI/RecordEditorGui.xaml b/Content.Client/_CD/Records/UI/RecordEditorGui.xaml
new file mode 100644
index 00000000000..40ba43c8b50
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/RecordEditorGui.xaml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_CD/Records/UI/RecordEditorGui.xaml.cs b/Content.Client/_CD/Records/UI/RecordEditorGui.xaml.cs
new file mode 100644
index 00000000000..631a68ea815
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/RecordEditorGui.xaml.cs
@@ -0,0 +1,155 @@
+using Content.Shared._CD.Records;
+using Content.Shared.Preferences;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._CD.Records.UI;
+
+///
+/// The record editor tab that gets "injected" into the character editor.
+///
+[GenerateTypedNameReferences]
+public sealed partial class RecordEditorGui : Control
+{
+ ///
+ /// Delegate that tells the editor to save records when the save button is pressed
+ ///
+ private readonly Action _updateProfileRecords;
+ private CharacterRecords _records = default!;
+
+ public RecordEditorGui(Action updateProfileRecords)
+ {
+ RobustXamlLoader.Load(this);
+ _updateProfileRecords = updateProfileRecords;
+
+ #region General
+
+ HeightEdit.OnTextChanged += args =>
+ {
+ if (!int.TryParse(args.Text, out var newHeight))
+ return;
+ UpdateImperialHeight(newHeight);
+ UpdateRecords(_records.WithHeight(newHeight));
+ };
+
+ WeightEdit.OnTextChanged += args =>
+ {
+ if (!int.TryParse(args.Text, out var newWeight))
+ return;
+ UpdateImperialWeight(newWeight);
+ UpdateRecords(_records.WithWeight(newWeight));
+ };
+
+ ContactNameEdit.OnTextChanged += args =>
+ {
+ UpdateRecords(_records.WithContactName(args.Text));
+ };
+
+ #endregion
+
+ #region Employment
+
+ WorkAuthCheckBox.OnToggled += args =>
+ {
+ UpdateRecords(_records.WithWorkAuth(args.Pressed));
+ };
+
+ #endregion
+
+ #region Security
+
+ IdentifyingFeaturesEdit.OnTextChanged += args =>
+ {
+ UpdateRecords(_records.WithIdentifyingFeatures(args.Text));
+ };
+
+ #endregion
+
+ #region Medical
+
+ AllergiesEdit.OnTextChanged += args =>
+ {
+ UpdateRecords(_records.WithAllergies(args.Text));
+ };
+
+ DrugAllergiesEdit.OnTextChanged += args =>
+ {
+ UpdateRecords(_records.WithDrugAllergies(args.Text));
+ };
+
+ PostmortemEdit.OnTextChanged += args =>
+ {
+ UpdateRecords(_records.WithPostmortemInstructions(args.Text));
+ };
+
+ #endregion
+
+ #region Entries
+
+ EntryEditorTabs.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-cd-records-employment"));
+ EntryEditorTabs.SetTabTitle(1, Loc.GetString("department-Medical"));
+ EntryEditorTabs.SetTabTitle(2, Loc.GetString("department-Security"));
+
+ EmploymentEntrySelector.OnUpdateEntries += args =>
+ {
+ UpdateRecords(_records.WithEmploymentEntries(args.Entries));
+ };
+
+ MedicalEntrySelector.OnUpdateEntries += args =>
+ {
+ UpdateRecords(_records.WithMedicalEntries(args.Entries));
+ };
+
+ SecurityEntrySelector.OnUpdateEntries += args =>
+ {
+ UpdateRecords(_records.WithSecurityEntries(args.Entries));
+ };
+
+ #endregion
+ }
+
+ public void Update(HumanoidCharacterProfile profile)
+ {
+ _records = profile.CDCharacterRecords ?? CharacterRecords.DefaultRecords();
+ EmploymentEntrySelector.UpdateContents(_records.EmploymentEntries);
+ MedicalEntrySelector.UpdateContents(_records.MedicalEntries);
+ SecurityEntrySelector.UpdateContents(_records.SecurityEntries);
+ UpdateWidgets();
+ }
+
+ private void UpdateRecords(CharacterRecords records)
+ {
+ records.EnsureValid();
+ _records = records;
+ _updateProfileRecords(_records);
+ UpdateWidgets();
+ }
+
+ private void UpdateWidgets()
+ {
+ HeightEdit.SetText(_records.Height.ToString());
+ UpdateImperialHeight(_records.Height);
+ WeightEdit.SetText(_records.Weight.ToString());
+ UpdateImperialWeight(_records.Weight);
+ ContactNameEdit.SetText(_records.EmergencyContactName);
+
+ WorkAuthCheckBox.Pressed = _records.HasWorkAuthorization;
+
+ IdentifyingFeaturesEdit.SetText(_records.IdentifyingFeatures);
+
+ AllergiesEdit.SetText(_records.Allergies);
+ DrugAllergiesEdit.SetText(_records.DrugAllergies);
+ PostmortemEdit.SetText(_records.PostmortemInstructions);
+ }
+
+ private void UpdateImperialHeight(int newHeight)
+ {
+ HeightImperialLabel.Text = UnitConversion.GetImperialDisplayLength(newHeight);
+ }
+
+ private void UpdateImperialWeight(int newWeight)
+ {
+ WeightImperialLabel.Text = UnitConversion.GetImperialDisplayMass(newWeight);
+ }
+}
diff --git a/Content.Client/_CD/Records/UI/RecordEntryEditPopup.xaml b/Content.Client/_CD/Records/UI/RecordEntryEditPopup.xaml
new file mode 100644
index 00000000000..c1855152a5d
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/RecordEntryEditPopup.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_CD/Records/UI/RecordEntryEditPopup.xaml.cs b/Content.Client/_CD/Records/UI/RecordEntryEditPopup.xaml.cs
new file mode 100644
index 00000000000..84cbc123da8
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/RecordEntryEditPopup.xaml.cs
@@ -0,0 +1,38 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared._CD.Records;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+
+namespace Content.Client._CD.Records.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class RecordEntryEditPopup : FancyWindow
+{
+ public RecordEntryEditPopup()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ DescriptionEdit.Placeholder =
+ new Rope.Leaf(Loc.GetString("cd-records-entry-edit-popup-description-placeholder"));
+
+ SaveButton.OnPressed += _ =>
+ {
+ Close();
+ };
+ }
+
+ public CharacterRecords.RecordEntry GetContents()
+ {
+ string desc = Rope.Collapse(DescriptionEdit.TextRope).Trim();
+ return new CharacterRecords.RecordEntry(TitleEdit.Text, InvolvedEdit.Text, desc);
+ }
+
+ public void SetContents(CharacterRecords.RecordEntry entry)
+ {
+ TitleEdit.Text = entry.Title;
+ InvolvedEdit.Text = entry.Involved;
+ DescriptionEdit.TextRope = new Rope.Leaf(entry.Description);
+ }
+}
diff --git a/Content.Client/_CD/Records/UI/RecordEntryViewPopup.xaml b/Content.Client/_CD/Records/UI/RecordEntryViewPopup.xaml
new file mode 100644
index 00000000000..2c8388e8f44
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/RecordEntryViewPopup.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_CD/Records/UI/RecordEntryViewPopup.xaml.cs b/Content.Client/_CD/Records/UI/RecordEntryViewPopup.xaml.cs
new file mode 100644
index 00000000000..73ce0a6eab4
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/RecordEntryViewPopup.xaml.cs
@@ -0,0 +1,34 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared._CD.Records;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.RichText;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+
+namespace Content.Client._CD.Records.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class RecordEntryViewPopup : FancyWindow
+{
+ // We don't permit headings because it looks ugly
+ private static readonly Type[] AllowedTags =
+ {
+ typeof(BoldItalicTag),
+ typeof(BoldTag),
+ typeof(BulletTag),
+ typeof(ColorTag),
+ typeof(ItalicTag)
+ };
+
+ public RecordEntryViewPopup()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void SetContents(CharacterRecords.RecordEntry entry)
+ {
+ EntryTitle.SetMessage(entry.Title);
+ EntryInvolved.SetMessage(entry.Involved);
+ EntryDesc.SetMessage(FormattedMessage.FromMarkupPermissive(entry.Description.Trim()), AllowedTags);
+ }
+}
diff --git a/Content.Client/_CD/Records/UI/RecordLongItemDisplay.cs b/Content.Client/_CD/Records/UI/RecordLongItemDisplay.cs
new file mode 100644
index 00000000000..7eca7114f71
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/RecordLongItemDisplay.cs
@@ -0,0 +1,65 @@
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client._CD.Records.UI;
+
+///
+/// Widget that displays the record on one line if it is short enough, and on two lines if it is
+/// too long. This should only be used if you know the length may be long enough to break things when
+/// using a normal Label.
+///
+public sealed class RecordLongItemDisplay : BoxContainer
+{
+ private const int MaxShortLength = 32;
+
+ public string? Title
+ {
+ get => _titleLabel.Text;
+ set => _titleLabel.Text = value;
+ }
+
+ // Row containing the title and short value
+ private readonly BoxContainer _firstRow = new()
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ HorizontalExpand = true
+ };
+ // Row containing the long value
+ private readonly BoxContainer _secondRow = new()
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ HorizontalExpand = true,
+ Visible = false,
+ };
+ private readonly Label _titleLabel = new();
+ private readonly Label _shortContents = new() { Visible = true, Align = Label.AlignMode.Right };
+ private readonly RichTextLabel _longContents = new() { HorizontalExpand = true };
+
+ public RecordLongItemDisplay()
+ {
+ Orientation = LayoutOrientation.Vertical;
+ _firstRow.AddChild(_titleLabel);
+ _firstRow.AddChild(new Control() { HorizontalExpand = true });
+ _firstRow.AddChild(_shortContents);
+ AddChild(_firstRow);
+ _secondRow.AddChild(new Control() { HorizontalExpand = true, SizeFlagsStretchRatio = 0.15f});
+ _secondRow.AddChild(_longContents);
+ AddChild(_secondRow);
+ }
+
+ public void SetValue(string s)
+ {
+ if (s.Length > MaxShortLength)
+ {
+ _longContents.SetMessage(s);
+ _secondRow.Visible = true;
+ _shortContents.Visible = false;
+ }
+ else
+ {
+ _shortContents.Text = s;
+ _shortContents.Visible = true;
+ _secondRow.Visible = false;
+ }
+ }
+}
diff --git a/Content.Client/_CD/Records/UI/UnitConversion.cs b/Content.Client/_CD/Records/UI/UnitConversion.cs
new file mode 100644
index 00000000000..22a852f2a92
--- /dev/null
+++ b/Content.Client/_CD/Records/UI/UnitConversion.cs
@@ -0,0 +1,16 @@
+namespace Content.Client._CD.Records.UI;
+
+public static class UnitConversion
+{
+ public static string GetImperialDisplayLength(int lengthCm)
+ {
+ int heightIn = (int) Math.Round(lengthCm * 0.3937007874 /* cm to in*/);
+ return $"({heightIn / 12}'{heightIn % 12}'')";
+ }
+
+ public static string GetImperialDisplayMass(int massKg)
+ {
+ int weightLbs = (int) Math.Round(massKg * 2.2046226218 /* kg to lbs */);
+ return $"({weightLbs} lbs)";
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs b/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs
index c57504764dc..286f92ffafc 100644
--- a/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs
+++ b/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.Database;
+using Content.Shared._CD.Records;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Preferences;
@@ -41,6 +42,7 @@ private static HumanoidCharacterProfile CharlieCharlieson()
"Charlie Charlieson",
"The biggest boy around.",
"Human",
+ 1,
21,
Sex.Male,
Gender.Epicene,
@@ -62,7 +64,9 @@ private static HumanoidCharacterProfile CharlieCharlieson()
},
PreferenceUnavailableMode.StayInLobby,
new List (),
- new List()
+ new List(),
+ // CD: test records
+ CharacterRecords.DefaultRecords()
);
}
diff --git a/Content.Server.Database/Migrations/Postgres/20230711102742_Height.Designer.cs b/Content.Server.Database/Migrations/Postgres/20230711102742_Height.Designer.cs
new file mode 100644
index 00000000000..c932ec93a64
--- /dev/null
+++ b/Content.Server.Database/Migrations/Postgres/20230711102742_Height.Designer.cs
@@ -0,0 +1,1368 @@
+//
+using System;
+using System.Net;
+using System.Text.Json;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ [DbContext(typeof(PostgresServerDbContext))]
+ [Migration("20230711102742_Height")]
+ partial class Height
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "7.0.4")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AdminId")
+ .HasColumnType("uuid")
+ .HasColumnName("admin_id");
+
+ b.Property("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.Property("Negative")
+ .HasColumnType("boolean")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property("Impact")
+ .HasColumnType("smallint")
+ .HasColumnName("impact");
+
+ b.Property("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("message");
+
+ b.Property("Type")
+ .HasColumnType("integer")
+ .HasColumnName("type");
+
+ b.HasKey("Id", "RoundId")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Message")
+ .HasAnnotation("Npgsql:TsVectorConfig", "english");
+
+ NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_log_round_id");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b =>
+ {
+ b.Property("Uid")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("uid");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Uid"));
+
+ b.Property("AdminLogId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_log_id");
+
+ b.Property("AdminLogRoundId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_log_round_id");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Uid")
+ .HasName("PK_admin_log_entity");
+
+ b.HasIndex("AdminLogId", "AdminLogRoundId")
+ .HasDatabaseName("IX_admin_log_entity_admin_log_id_admin_log_round_id");
+
+ b.ToTable("admin_log_entity", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("LogId")
+ .HasColumnType("integer")
+ .HasColumnName("log_id");
+
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.HasKey("PlayerUserId", "LogId", "RoundId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("LogId", "RoundId");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_notes_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("ShownToPlayer")
+ .HasColumnType("boolean")
+ .HasColumnName("shown_to_player");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("antag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AntagName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("antag_name");
+
+ b.Property("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_antag");
+
+ b.HasIndex("ProfileId", "AntagName")
+ .IsUnique();
+
+ b.ToTable("antag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("assigned_user_id_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_assigned_user_id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("assigned_user_id", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("connection_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Address")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property("Denied")
+ .HasColumnType("smallint")
+ .HasColumnName("denied");
+
+ b.Property("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property("Time")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("time");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_connection_log");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("connection_log", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("job_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("JobName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("job_name");
+
+ b.Property("Priority")
+ .HasColumnType("integer")
+ .HasColumnName("priority");
+
+ b.Property("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_job");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "JobName")
+ .IsUnique();
+
+ b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
+ .IsUnique()
+ .HasFilter("priority = 3");
+
+ b.ToTable("job", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("play_time_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("PlayerId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_id");
+
+ b.Property("TimeSpent")
+ .HasColumnType("interval")
+ .HasColumnName("time_spent");
+
+ b.Property("Tracker")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("tracker");
+
+ b.HasKey("Id")
+ .HasName("PK_play_time");
+
+ b.HasIndex("PlayerId", "Tracker")
+ .IsUnique();
+
+ b.ToTable("play_time", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("player_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("FirstSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("first_seen_time");
+
+ b.Property("LastReadRules")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_read_rules");
+
+ b.Property("LastSeenAddress")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("last_seen_address");
+
+ b.Property("LastSeenHWId")
+ .HasColumnType("bytea")
+ .HasColumnName("last_seen_hwid");
+
+ b.Property("LastSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_seen_time");
+
+ b.Property("LastSeenUserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("last_seen_user_name");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_player");
+
+ b.HasAlternateKey("UserId")
+ .HasName("ak_player_user_id");
+
+ b.HasIndex("LastSeenUserName");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("player", null, t =>
+ {
+ t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AdminOOCColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("admin_ooc_color");
+
+ b.Property("SelectedCharacterSlot")
+ .HasColumnType("integer")
+ .HasColumnName("selected_character_slot");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_preference");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("preference", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Age")
+ .HasColumnType("integer")
+ .HasColumnName("age");
+
+ b.Property("Backpack")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("backpack");
+
+ b.Property("CharacterName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("char_name");
+
+ b.Property("Clothing")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("clothing");
+
+ b.Property("EyeColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("eye_color");
+
+ b.Property("FacialHairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_color");
+
+ b.Property("FacialHairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_name");
+
+ b.Property("FlavorText")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flavor_text");
+
+ b.Property("Gender")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("gender");
+
+ b.Property("HairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_color");
+
+ b.Property("HairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_name");
+
+ b.Property("Height")
+ .HasColumnType("real")
+ .HasColumnName("height");
+
+ b.Property("Markings")
+ .HasColumnType("jsonb")
+ .HasColumnName("markings");
+
+ b.Property("PreferenceId")
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ b.Property("PreferenceUnavailable")
+ .HasColumnType("integer")
+ .HasColumnName("pref_unavailable");
+
+ b.Property("Sex")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("sex");
+
+ b.Property("SkinColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("skin_color");
+
+ b.Property("Slot")
+ .HasColumnType("integer")
+ .HasColumnName("slot");
+
+ b.Property("Species")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("species");
+
+ b.HasKey("Id")
+ .HasName("PK_profile");
+
+ b.HasIndex("PreferenceId")
+ .HasDatabaseName("IX_profile_preference_id");
+
+ b.HasIndex("Slot", "PreferenceId")
+ .IsUnique();
+
+ b.ToTable("profile", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ServerId")
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ b.HasKey("Id")
+ .HasName("PK_round");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_round_server_id");
+
+ b.ToTable("round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_server");
+
+ b.ToTable("server", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property?>("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property("AutoDelete")
+ .HasColumnType("boolean")
+ .HasColumnName("auto_delete");
+
+ b.Property("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property("ExemptFlags")
+ .HasColumnType("integer")
+ .HasColumnName("exempt_flags");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("server_ban", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
+ {
+ b.Property("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("Flags")
+ .HasColumnType("integer")
+ .HasColumnName("flags");
+
+ b.HasKey("UserId")
+ .HasName("PK_server_ban_exemption");
+
+ b.ToTable("server_ban_exemption", null, t =>
+ {
+ t.HasCheckConstraint("FlagsNotZero", "flags != 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_ban_hit_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property("ConnectionId")
+ .HasColumnType("integer")
+ .HasColumnName("connection_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban_hit");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_server_ban_hit_ban_id");
+
+ b.HasIndex("ConnectionId")
+ .HasDatabaseName("IX_server_ban_hit_connection_id");
+
+ b.ToTable("server_ban_hit", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_role_ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property?>("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_id");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("server_role_ban", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("role_unban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property("UnbanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("unban_time");
+
+ b.Property("UnbanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_role_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("unban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property("UnbanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("unban_time");
+
+ b.Property("UnbanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("trait_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property("TraitName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("trait_name");
+
+ b.HasKey("Id")
+ .HasName("PK_trait");
+
+ b.HasIndex("ProfileId", "TraitName")
+ .IsUnique();
+
+ b.ToTable("trait", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("uploaded_resource_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Data")
+ .IsRequired()
+ .HasColumnType("bytea")
+ .HasColumnName("data");
+
+ b.Property("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("path");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_uploaded_resource_log");
+
+ b.ToTable("uploaded_resource_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Whitelist", b =>
+ {
+ b.Property("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_whitelist");
+
+ b.ToTable("whitelist", (string)null);
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.Property("PlayersId")
+ .HasColumnType("integer")
+ .HasColumnName("players_id");
+
+ b.Property("RoundsId")
+ .HasColumnType("integer")
+ .HasColumnName("rounds_id");
+
+ b.HasKey("PlayersId", "RoundsId")
+ .HasName("PK_player_round");
+
+ b.HasIndex("RoundsId")
+ .HasDatabaseName("IX_player_round_rounds_id");
+
+ b.ToTable("player_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
+ .WithMany("Admins")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_admin_rank_admin_rank_id");
+
+ b.Navigation("AdminRank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.Admin", "Admin")
+ .WithMany("Flags")
+ .HasForeignKey("AdminId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_flag_admin_admin_id");
+
+ b.Navigation("Admin");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany("AdminLogs")
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_round_round_id");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminLog", null)
+ .WithMany("Entities")
+ .HasForeignKey("AdminLogId", "AdminLogRoundId")
+ .HasConstraintName("FK_admin_log_entity_admin_log_admin_log_id_admin_log_round_id");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminLogs")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.AdminLog", "Log")
+ .WithMany("Players")
+ .HasForeignKey("LogId", "RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_admin_log_log_id_round_id");
+
+ b.Navigation("Log");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminNotesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_notes_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminNotesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .HasConstraintName("FK_admin_notes_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminNotesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_notes_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminNotesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_notes_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_notes_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "Rank")
+ .WithMany("Flags")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id");
+
+ b.Navigation("Rank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Antags")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_antag_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Jobs")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_job_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.HasOne("Content.Server.Database.Preference", "Preference")
+ .WithMany("Profiles")
+ .HasForeignKey("PreferenceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_preference_preference_id");
+
+ b.Navigation("Preference");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("Rounds")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_round_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithMany("BanHits")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
+ .WithMany("BanHits")
+ .HasForeignKey("ConnectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_connection_log_connection_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Connection");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_unban_server_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_trait_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", null)
+ .WithMany()
+ .HasForeignKey("PlayersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_player_players_id");
+
+ b.HasOne("Content.Server.Database.Round", null)
+ .WithMany()
+ .HasForeignKey("RoundsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_round_rounds_id");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Navigation("Entities");
+
+ b.Navigation("Players");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Navigation("Admins");
+
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Navigation("BanHits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Navigation("AdminLogs");
+
+ b.Navigation("AdminNotesCreated");
+
+ b.Navigation("AdminNotesDeleted");
+
+ b.Navigation("AdminNotesLastEdited");
+
+ b.Navigation("AdminNotesReceived");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Navigation("Profiles");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Navigation("Antags");
+
+ b.Navigation("Jobs");
+
+ b.Navigation("Traits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Navigation("AdminLogs");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Navigation("Rounds");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Navigation("BanHits");
+
+ b.Navigation("Unban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Navigation("Unban");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Content.Server.Database/Migrations/Postgres/20230711102742_Height.cs b/Content.Server.Database/Migrations/Postgres/20230711102742_Height.cs
new file mode 100644
index 00000000000..8647022a5c4
--- /dev/null
+++ b/Content.Server.Database/Migrations/Postgres/20230711102742_Height.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ ///
+ public partial class Height : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "height",
+ table: "profile",
+ type: "REAL",
+ nullable: false,
+ defaultValue: 1f);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "height",
+ table: "profile");
+ }
+ }
+}
diff --git a/Content.Server.Database/Migrations/Postgres/20240119204209_CDCharacterRecords.Designer.cs b/Content.Server.Database/Migrations/Postgres/20240119204209_CDCharacterRecords.Designer.cs
new file mode 100644
index 00000000000..f3f2766d9fe
--- /dev/null
+++ b/Content.Server.Database/Migrations/Postgres/20240119204209_CDCharacterRecords.Designer.cs
@@ -0,0 +1,1764 @@
+//
+using System;
+using System.Net;
+using System.Text.Json;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ [DbContext(typeof(PostgresServerDbContext))]
+ [Migration("20240119204209_CDCharacterRecords")]
+ partial class CDCharacterRecords
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "7.0.4")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AdminId")
+ .HasColumnType("uuid")
+ .HasColumnName("admin_id");
+
+ b.Property("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.Property("Negative")
+ .HasColumnType("boolean")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("Id")
+ .HasColumnType("integer")
+ .HasColumnName("admin_log_id");
+
+ b.Property("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property("Impact")
+ .HasColumnType("smallint")
+ .HasColumnName("impact");
+
+ b.Property("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("message");
+
+ b.Property("Type")
+ .HasColumnType("integer")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Message")
+ .HasAnnotation("Npgsql:TsVectorConfig", "english");
+
+ NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("LogId")
+ .HasColumnType("integer")
+ .HasColumnName("log_id");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_messages_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("Seen")
+ .HasColumnType("boolean")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_notes_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property("Secret")
+ .HasColumnType("boolean")
+ .HasColumnName("secret");
+
+ b.Property("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_watchlists_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property