Station records (#8720)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Content.Client.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.Access.Components.SharedIdCardConsoleComponent;
|
||||
@@ -36,6 +37,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
_window = new IdCardConsoleWindow(this, _prototypeManager, accessLevels) {Title = _entityManager.GetComponent<MetaDataComponent>(Owner.Owner).EntityName};
|
||||
|
||||
_window.CrewManifestButton.OnPressed += _ => SendMessage(new CrewManifestOpenUiMessage());
|
||||
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
|
||||
_window.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(TargetIdCardSlotId));
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
MinSize="650 290">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<GridContainer Columns="3">
|
||||
<Label Text="{Loc 'id-card-console-window-privileged-id'}" />
|
||||
<Button Name="PrivilegedIdButton" Access="Public"/>
|
||||
<Label Name="PrivilegedIdLabel" />
|
||||
<GridContainer Columns="2">
|
||||
<GridContainer Columns="3" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'id-card-console-window-privileged-id'}" />
|
||||
<Button Name="PrivilegedIdButton" Access="Public"/>
|
||||
<Label Name="PrivilegedIdLabel" />
|
||||
|
||||
<Label Text="{Loc 'id-card-console-window-target-id'}" />
|
||||
<Button Name="TargetIdButton" Access="Public"/>
|
||||
<Label Name="TargetIdLabel" />
|
||||
<Label Text="{Loc 'id-card-console-window-target-id'}" />
|
||||
<Button Name="TargetIdButton" Access="Public"/>
|
||||
<Label Name="TargetIdLabel" />
|
||||
</GridContainer>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Button Name="CrewManifestButton" Access="Public" Text="{Loc 'crew-manifest-button-label'}" />
|
||||
</BoxContainer>
|
||||
</GridContainer>
|
||||
<Control MinSize="0 8" />
|
||||
<GridContainer Columns="3" HSeparationOverride="4">
|
||||
@@ -21,6 +26,10 @@
|
||||
<Button Name="JobTitleSaveButton" Text="{Loc 'id-card-console-window-save-button'}" Disabled="True" />
|
||||
</GridContainer>
|
||||
<Control MinSize="0 8" />
|
||||
<GridContainer Columns="2">
|
||||
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
|
||||
<OptionButton Name="JobPresetOptionButton" />
|
||||
</GridContainer>
|
||||
<GridContainer Name="AccessLevelGrid" Columns="5" HorizontalAlignment="Center">
|
||||
|
||||
<!-- Access level buttons are added here by the C# code -->
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
@@ -16,9 +17,12 @@ namespace Content.Client.Access.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class IdCardConsoleWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private readonly IdCardConsoleBoundUserInterface _owner;
|
||||
|
||||
private readonly Dictionary<string, Button> _accessButtons = new();
|
||||
private readonly List<string> _jobPrototypeIds = new();
|
||||
|
||||
private string? _lastFullName;
|
||||
private string? _lastJobTitle;
|
||||
@@ -26,6 +30,7 @@ namespace Content.Client.Access.UI
|
||||
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager, List<string> accessLevels)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_owner = owner;
|
||||
|
||||
@@ -43,6 +48,21 @@ namespace Content.Client.Access.UI
|
||||
};
|
||||
JobTitleSaveButton.OnPressed += _ => SubmitData();
|
||||
|
||||
var jobs = _prototypeManager.EnumeratePrototypes<JobPrototype>();
|
||||
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
if (!job.SetPreference)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_jobPrototypeIds.Add(job.ID);
|
||||
JobPresetOptionButton.AddItem(Loc.GetString(job.Name), _jobPrototypeIds.Count - 1);
|
||||
}
|
||||
|
||||
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
|
||||
|
||||
foreach (var access in accessLevels)
|
||||
{
|
||||
if (!prototypeManager.TryIndex<AccessLevelPrototype>(access, out var accessLevel))
|
||||
@@ -62,6 +82,56 @@ namespace Content.Client.Access.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAllAccess()
|
||||
{
|
||||
foreach (var button in _accessButtons.Values)
|
||||
{
|
||||
if (button.Pressed)
|
||||
{
|
||||
button.Pressed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(_jobPrototypeIds[args.Id], out JobPrototype? job))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
JobTitleLineEdit.Text = Loc.GetString(job.Name);
|
||||
|
||||
ClearAllAccess();
|
||||
|
||||
// this is a sussy way to do this
|
||||
foreach (var access in job.Access)
|
||||
{
|
||||
if (_accessButtons.TryGetValue(access, out var button))
|
||||
{
|
||||
button.Pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var group in job.AccessGroups)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(group, out AccessGroupPrototype? groupPrototype))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var access in groupPrototype.Tags)
|
||||
{
|
||||
if (_accessButtons.TryGetValue(access, out var button))
|
||||
{
|
||||
button.Pressed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubmitData();
|
||||
}
|
||||
|
||||
public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
|
||||
{
|
||||
PrivilegedIdButton.Text = state.IsPrivilegedIdPresent
|
||||
@@ -100,6 +170,8 @@ namespace Content.Client.Access.UI
|
||||
|
||||
JobTitleSaveButton.Disabled = !interfaceEnabled || !jobTitleDirty;
|
||||
|
||||
JobPresetOptionButton.Disabled = !interfaceEnabled;
|
||||
|
||||
foreach (var (accessName, button) in _accessButtons)
|
||||
{
|
||||
button.Disabled = !interfaceEnabled;
|
||||
|
||||
51
Content.Client/CrewManifest/CrewManifestEui.cs
Normal file
51
Content.Client/CrewManifest/CrewManifestEui.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.CrewManifest;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class CrewManifestEui : BaseEui
|
||||
{
|
||||
private readonly ClientGameTicker _gameTicker;
|
||||
private readonly CrewManifestUi _window;
|
||||
|
||||
public CrewManifestEui()
|
||||
{
|
||||
_gameTicker = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ClientGameTicker>();
|
||||
_window = new();
|
||||
|
||||
_window.OnClose += () =>
|
||||
{
|
||||
SendMessage(new CrewManifestEuiClosed());
|
||||
};
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
base.HandleState(state);
|
||||
|
||||
if (state is not CrewManifestEuiState cast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_window.Populate(cast.StationName, cast.Entries);
|
||||
}
|
||||
}
|
||||
82
Content.Client/CrewManifest/CrewManifestSystem.cs
Normal file
82
Content.Client/CrewManifest/CrewManifestSystem.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.CrewManifest;
|
||||
|
||||
public sealed class CrewManifestSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private Dictionary<string, Dictionary<string, int>> _jobDepartmentLookup = new();
|
||||
private HashSet<string> _departments = new();
|
||||
|
||||
public IReadOnlySet<string> Departments => _departments;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
BuildDepartmentLookup();
|
||||
_prototypeManager.PrototypesReloaded += OnPrototypesReload;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_prototypeManager.PrototypesReloaded -= OnPrototypesReload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests a crew manifest from the server.
|
||||
/// </summary>
|
||||
/// <param name="uid">EntityUid of the entity we're requesting the crew manifest from.</param>
|
||||
public void RequestCrewManifest(EntityUid uid)
|
||||
{
|
||||
RaiseNetworkEvent(new RequestCrewManifestMessage(uid));
|
||||
}
|
||||
|
||||
private void OnPrototypesReload(PrototypesReloadedEventArgs _)
|
||||
{
|
||||
_jobDepartmentLookup.Clear();
|
||||
_departments.Clear();
|
||||
|
||||
BuildDepartmentLookup();
|
||||
}
|
||||
|
||||
private void BuildDepartmentLookup()
|
||||
{
|
||||
foreach (var department in _prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
|
||||
{
|
||||
_departments.Add(department.ID);
|
||||
|
||||
for (var i = 1; i <= department.Roles.Count; i++)
|
||||
{
|
||||
if (!_jobDepartmentLookup.TryGetValue(department.Roles[i - 1], out var departments))
|
||||
{
|
||||
departments = new();
|
||||
_jobDepartmentLookup.Add(department.Roles[i - 1], departments);
|
||||
}
|
||||
|
||||
departments.Add(department.ID, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetDepartmentOrder(string department, string jobPrototype)
|
||||
{
|
||||
if (!Departments.Contains(department))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!_jobDepartmentLookup.TryGetValue(jobPrototype, out var departments))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return departments.TryGetValue(department, out var order)
|
||||
? order
|
||||
: -1;
|
||||
}
|
||||
}
|
||||
21
Content.Client/CrewManifest/CrewManifestUi.xaml
Normal file
21
Content.Client/CrewManifest/CrewManifestUi.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.HUD.UI"
|
||||
Title="{Loc 'crew-manifest-window-title'}"
|
||||
MinSize="450 750">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
|
||||
<ui:StripeBack Name="StationNameContainer">
|
||||
<PanelContainer>
|
||||
<Label Name="StationName" Align="Center" />
|
||||
</PanelContainer>
|
||||
</ui:StripeBack>
|
||||
<BoxContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<!-- this MIGHT have race conditions -->
|
||||
<BoxContainer Name="CrewManifestListing" Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'crew-manifest-no-valid-station'}" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<!-- Crew manifest goes here. -->
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
186
Content.Client/CrewManifest/CrewManifestUi.xaml.cs
Normal file
186
Content.Client/CrewManifest/CrewManifestUi.xaml.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.CrewManifest;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CrewManifestUi : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
private readonly CrewManifestSystem _crewManifestSystem;
|
||||
|
||||
private EntityUid? _station;
|
||||
|
||||
public CrewManifestUi()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_crewManifestSystem = _entitySystemManager.GetEntitySystem<CrewManifestSystem>();
|
||||
|
||||
StationName.AddStyleClass("LabelBig");
|
||||
}
|
||||
|
||||
public void Populate(string name, CrewManifestEntries? entries)
|
||||
{
|
||||
CrewManifestListing.DisposeAllChildren();
|
||||
CrewManifestListing.RemoveAllChildren();
|
||||
|
||||
StationNameContainer.Visible = entries != null;
|
||||
StationName.Text = name;
|
||||
|
||||
if (entries == null) return;
|
||||
|
||||
var entryList = SortEntries(entries);
|
||||
|
||||
foreach (var item in entryList)
|
||||
{
|
||||
CrewManifestListing.AddChild(new CrewManifestSection(item.section, item.entries, _resourceCache, _crewManifestSystem));
|
||||
}
|
||||
}
|
||||
|
||||
private List<(string section, List<CrewManifestEntry> entries)> SortEntries(CrewManifestEntries entries)
|
||||
{
|
||||
var entryDict = new Dictionary<string, List<CrewManifestEntry>>();
|
||||
|
||||
foreach (var entry in entries.Entries)
|
||||
{
|
||||
foreach (var department in _prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
|
||||
{
|
||||
// this is a little expensive, and could be better
|
||||
if (department.Roles.Contains(entry.JobPrototype))
|
||||
{
|
||||
entryDict.GetOrNew(department.ID).Add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var entryList = new List<(string section, List<CrewManifestEntry> entries)>();
|
||||
|
||||
foreach (var (section, listing) in entryDict)
|
||||
{
|
||||
entryList.Add((section, listing));
|
||||
}
|
||||
|
||||
var sortOrder = _configManager.GetCVar(CCVars.CrewManifestOrdering).Split(",").ToList();
|
||||
|
||||
entryList.Sort((a, b) =>
|
||||
{
|
||||
var ai = sortOrder.IndexOf(a.section);
|
||||
var bi = sortOrder.IndexOf(b.section);
|
||||
|
||||
// this is up here so -1 == -1 occurs first
|
||||
if (ai == bi)
|
||||
return 0;
|
||||
|
||||
if (ai == -1)
|
||||
return -1;
|
||||
|
||||
if (bi == -1)
|
||||
return 1;
|
||||
|
||||
return ai.CompareTo(bi);
|
||||
});
|
||||
|
||||
return entryList;
|
||||
}
|
||||
|
||||
private sealed class CrewManifestSection : BoxContainer
|
||||
{
|
||||
public CrewManifestSection(string sectionTitle, List<CrewManifestEntry> entries, IResourceCache cache, CrewManifestSystem crewManifestSystem)
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
HorizontalExpand = true;
|
||||
|
||||
AddChild(new Label()
|
||||
{
|
||||
StyleClasses = { "LabelBig" },
|
||||
Text = Loc.GetString(sectionTitle)
|
||||
});
|
||||
|
||||
entries.Sort((a, b) =>
|
||||
{
|
||||
var posA = crewManifestSystem.GetDepartmentOrder(sectionTitle, a.JobPrototype);
|
||||
var posB = crewManifestSystem.GetDepartmentOrder(sectionTitle, b.JobPrototype);
|
||||
|
||||
return posA.CompareTo(posB);
|
||||
});
|
||||
|
||||
var gridContainer = new GridContainer()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Columns = 2
|
||||
};
|
||||
|
||||
AddChild(gridContainer);
|
||||
|
||||
var path = new ResourcePath("/Textures/Interface/Misc/job_icons.rsi");
|
||||
cache.TryGetResource(path, out RSIResource? rsi);
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var name = new Label()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Text = entry.Name
|
||||
};
|
||||
|
||||
var titleContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
var title = new Label()
|
||||
{
|
||||
Text = Loc.GetString(entry.JobTitle)
|
||||
};
|
||||
|
||||
|
||||
if (rsi != null)
|
||||
{
|
||||
var icon = new TextureRect()
|
||||
{
|
||||
TextureScale = (2, 2),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered
|
||||
};
|
||||
|
||||
if (rsi.RSI.TryGetState(entry.JobIcon, out _))
|
||||
{
|
||||
var specifier = new SpriteSpecifier.Rsi(path, entry.JobIcon);
|
||||
icon.Texture = specifier.Frame0();
|
||||
}
|
||||
else if (rsi.RSI.TryGetState("Unknown", out _))
|
||||
{
|
||||
var specifier = new SpriteSpecifier.Rsi(path, "Unknown");
|
||||
icon.Texture = specifier.Frame0();
|
||||
}
|
||||
|
||||
titleContainer.AddChild(icon);
|
||||
titleContainer.AddChild(title);
|
||||
}
|
||||
else
|
||||
{
|
||||
titleContainer.AddChild(title);
|
||||
}
|
||||
|
||||
gridContainer.AddChild(name);
|
||||
gridContainer.AddChild(titleContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
using System.Linq;
|
||||
using Content.Client.CrewManifest;
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Client.HUD.UI;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
@@ -17,6 +22,7 @@ namespace Content.Client.LateJoin
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
public event Action<(EntityUid, string)> SelectedId;
|
||||
|
||||
@@ -109,6 +115,21 @@ namespace Content.Client.LateJoin
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (_configManager.GetCVar<bool>(CCVars.CrewManifestWithoutEntity))
|
||||
{
|
||||
var crewManifestButton = new Button()
|
||||
{
|
||||
Text = Loc.GetString("crew-manifest-button-label")
|
||||
};
|
||||
crewManifestButton.OnPressed += args =>
|
||||
{
|
||||
EntitySystem.Get<CrewManifestSystem>().RequestCrewManifest(id);
|
||||
};
|
||||
|
||||
_base.AddChild(crewManifestButton);
|
||||
}
|
||||
|
||||
var jobListScroll = new ScrollContainer()
|
||||
{
|
||||
VerticalExpand = true,
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.PDA;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.PDA
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class PDABoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
private PDAMenu? _menu;
|
||||
|
||||
public PDABoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
@@ -27,6 +33,15 @@ namespace Content.Client.PDA
|
||||
SendMessage(new PDAToggleFlashlightMessage());
|
||||
};
|
||||
|
||||
if (_configManager.GetCVar(CCVars.CrewManifestUnsecure))
|
||||
{
|
||||
_menu.CrewManifestButton.Visible = true;
|
||||
_menu.CrewManifestButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new CrewManifestOpenUiMessage());
|
||||
};
|
||||
}
|
||||
|
||||
_menu.EjectIdButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new ItemSlotButtonPressedEvent(PDAComponent.PDAIdSlotId));
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
Access="Public"
|
||||
Text="{Loc 'comp-pda-ui-toggle-flashlight-button'}"
|
||||
ToggleMode="True" />
|
||||
<Button Name="CrewManifestButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'crew-manifest-button-label'}"
|
||||
Visible="False" />
|
||||
<Button Name="ActivateUplinkButton"
|
||||
Access="Public"
|
||||
Text="{Loc 'pda-bound-user-interface-uplink-tab-title'}" />
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.StationRecords;
|
||||
|
||||
public sealed class GeneralStationRecordConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private GeneralStationRecordConsoleWindow? _window = default!;
|
||||
|
||||
public GeneralStationRecordConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new();
|
||||
_window.OnKeySelected += OnKeySelected;
|
||||
_window.OnClose += Close;
|
||||
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
private void OnKeySelected(StationRecordKey? key)
|
||||
{
|
||||
SendMessage(new SelectGeneralStationRecord(key));
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not GeneralStationRecordConsoleState cast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_window?.UpdateState(cast);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
_window?.Close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'general-station-record-console-window-title'}"
|
||||
MinSize="750 500">
|
||||
<BoxContainer>
|
||||
<!-- Record listing -->
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" MinWidth="250" VerticalExpand="True">
|
||||
<Label Name="RecordListingStatus" Visible="False" />
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<ItemList Name="RecordListing" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5">
|
||||
<Label Name="RecordContainerStatus" Visible="False" Text="{Loc 'general-station-record-console-select-record-info'}"/>
|
||||
<BoxContainer Name="RecordContainer" Orientation="Vertical" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,126 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.StationRecords;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
|
||||
{
|
||||
public Action<StationRecordKey?>? OnKeySelected;
|
||||
private bool _isPopulating;
|
||||
|
||||
public GeneralStationRecordConsoleWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
RecordListing.OnItemSelected += args =>
|
||||
{
|
||||
if (_isPopulating || RecordListing[args.ItemIndex].Metadata is not StationRecordKey cast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnKeySelected?.Invoke(cast);
|
||||
};
|
||||
|
||||
RecordListing.OnItemDeselected += _ =>
|
||||
{
|
||||
if (!_isPopulating)
|
||||
OnKeySelected?.Invoke(null);
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateState(GeneralStationRecordConsoleState state)
|
||||
{
|
||||
if (state.RecordListing == null)
|
||||
{
|
||||
RecordListingStatus.Visible = true;
|
||||
RecordListing.Visible = false;
|
||||
RecordListingStatus.Text = Loc.GetString("general-station-record-console-empty-state");
|
||||
return;
|
||||
}
|
||||
|
||||
RecordListingStatus.Visible = false;
|
||||
RecordListing.Visible = true;
|
||||
PopulateRecordListing(state.RecordListing!, state.SelectedKey);
|
||||
|
||||
RecordContainerStatus.Visible = state.Record == null;
|
||||
|
||||
if (state.Record != null)
|
||||
{
|
||||
RecordContainerStatus.Visible = state.SelectedKey == null;
|
||||
RecordContainerStatus.Text = state.SelectedKey == null
|
||||
? Loc.GetString("general-station-record-console-no-record-found")
|
||||
: Loc.GetString("general-station-record-console-select-record-info");
|
||||
PopulateRecordContainer(state.Record);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecordContainer.DisposeAllChildren();
|
||||
RecordContainer.RemoveAllChildren();
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateRecordListing(Dictionary<StationRecordKey, string> listing, StationRecordKey? selected)
|
||||
{
|
||||
RecordListing.Clear();
|
||||
RecordListing.ClearSelected();
|
||||
|
||||
_isPopulating = true;
|
||||
foreach (var (key, name) in listing)
|
||||
{
|
||||
var item = RecordListing.AddItem(name);
|
||||
item.Metadata = key;
|
||||
|
||||
if (selected != null && key.ID == selected.Value.ID)
|
||||
{
|
||||
item.Selected = true;
|
||||
}
|
||||
}
|
||||
_isPopulating = false;
|
||||
|
||||
RecordListing.SortItemsByText();
|
||||
}
|
||||
|
||||
private void PopulateRecordContainer(GeneralStationRecord record)
|
||||
{
|
||||
RecordContainer.DisposeAllChildren();
|
||||
RecordContainer.RemoveAllChildren();
|
||||
// sure
|
||||
var recordControls = new Control[]
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = record.Name,
|
||||
StyleClasses = { "LabelBig" }
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-age", ("age", record.Age.ToString()))
|
||||
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-title", ("job", Loc.GetString(record.JobTitle)))
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-species", ("species", record.Species))
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-gender", ("gender", record.Gender.ToString()))
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var control in recordControls)
|
||||
{
|
||||
RecordContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
@@ -91,6 +93,25 @@ namespace Content.Server.Access.Components
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{_entities.ToPrettyString(player):player} has modified {_entities.ToPrettyString(targetIdEntity):entity} with the following accesses: [{string.Join(", ", newAccessList)}]");
|
||||
|
||||
UpdateStationRecord(targetIdEntity, newFullName, newJobTitle);
|
||||
}
|
||||
|
||||
private void UpdateStationRecord(EntityUid idCard, string newFullName, string newJobTitle)
|
||||
{
|
||||
var station = EntitySystem.Get<StationSystem>().GetOwningStation(Owner);
|
||||
var recordSystem = EntitySystem.Get<StationRecordsSystem>();
|
||||
if (station == null
|
||||
|| !_entities.TryGetComponent(idCard, out StationRecordKeyStorageComponent? keyStorage)
|
||||
|| keyStorage.Key == null
|
||||
|| !recordSystem.TryGetRecord(station.Value, keyStorage.Key.Value, out GeneralStationRecord? record))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
record.Name = newFullName;
|
||||
record.JobTitle = newJobTitle;
|
||||
|
||||
recordSystem.Synchronize(station.Value);
|
||||
}
|
||||
|
||||
public void UpdateUserInterface()
|
||||
|
||||
47
Content.Server/CrewManifest/CrewManifestEui.cs
Normal file
47
Content.Server/CrewManifest/CrewManifestEui.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Eui;
|
||||
|
||||
namespace Content.Server.CrewManifest;
|
||||
|
||||
public sealed class CrewManifestEui : BaseEui
|
||||
{
|
||||
private readonly CrewManifestSystem _crewManifest;
|
||||
|
||||
/// <summary>
|
||||
/// Station this EUI instance is currently tracking.
|
||||
/// </summary>
|
||||
private readonly EntityUid _station;
|
||||
|
||||
/// <summary>
|
||||
/// Current owner of this UI, if it has one. This is
|
||||
/// to ensure that if a BUI is closed, the EUIs related
|
||||
/// to the BUI are closed as well.
|
||||
/// </summary>
|
||||
public readonly EntityUid? Owner;
|
||||
|
||||
public CrewManifestEui(EntityUid station, EntityUid? owner, CrewManifestSystem crewManifestSystem)
|
||||
{
|
||||
_station = station;
|
||||
Owner = owner;
|
||||
_crewManifest = crewManifestSystem;
|
||||
}
|
||||
|
||||
public override CrewManifestEuiState GetNewState()
|
||||
{
|
||||
var (name, entries) = _crewManifest.GetCrewManifest(_station);
|
||||
return new(name, entries);
|
||||
}
|
||||
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
base.HandleMessage(msg);
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case CrewManifestEuiClosed:
|
||||
_crewManifest.CloseEui(_station, Player, Owner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
263
Content.Server/CrewManifest/CrewManifestSystem.cs
Normal file
263
Content.Server/CrewManifest/CrewManifestSystem.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.CrewManifest;
|
||||
|
||||
public sealed class CrewManifestSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _recordsSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Cached crew manifest entries. The alternative is to outright
|
||||
/// rebuild the crew manifest every time the state is requested:
|
||||
/// this is inefficient.
|
||||
/// </summary>
|
||||
private readonly Dictionary<EntityUid, CrewManifestEntries> _cachedEntries = new();
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<IPlayerSession, CrewManifestEui>> _openEuis = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<AfterGeneralRecordCreatedEvent>(AfterGeneralRecordCreated);
|
||||
SubscribeLocalEvent<RecordModifiedEvent>(OnRecordModified);
|
||||
SubscribeLocalEvent<CrewManifestViewerComponent, BoundUIClosedEvent>(OnBoundUiClose);
|
||||
SubscribeLocalEvent<CrewManifestViewerComponent, CrewManifestOpenUiMessage>(OpenEuiFromBui);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
SubscribeNetworkEvent<RequestCrewManifestMessage>(OnRequestCrewManifest);
|
||||
}
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
foreach (var (_, euis) in _openEuis)
|
||||
{
|
||||
foreach (var (_, eui) in euis)
|
||||
{
|
||||
eui.Close();
|
||||
}
|
||||
}
|
||||
|
||||
_openEuis.Clear();
|
||||
_cachedEntries.Clear();
|
||||
}
|
||||
|
||||
private void OnRequestCrewManifest(RequestCrewManifestMessage message, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession is not IPlayerSession sessionCast
|
||||
|| !_configManager.GetCVar(CCVars.CrewManifestWithoutEntity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OpenEui(message.Id, sessionCast);
|
||||
}
|
||||
|
||||
// Not a big fan of this one. Rebuilds the crew manifest every time
|
||||
// somebody spawns in, meaning that at round start, it rebuilds the crew manifest
|
||||
// wrt the amount of players readied up.
|
||||
private void AfterGeneralRecordCreated(AfterGeneralRecordCreatedEvent ev)
|
||||
{
|
||||
BuildCrewManifest(ev.Key.OriginStation);
|
||||
UpdateEuis(ev.Key.OriginStation);
|
||||
}
|
||||
|
||||
private void OnRecordModified(RecordModifiedEvent ev)
|
||||
{
|
||||
BuildCrewManifest(ev.Key.OriginStation);
|
||||
UpdateEuis(ev.Key.OriginStation);
|
||||
}
|
||||
|
||||
private void OnBoundUiClose(EntityUid uid, CrewManifestViewerComponent component, BoundUIClosedEvent ev)
|
||||
{
|
||||
var owningStation = _stationSystem.GetOwningStation(uid);
|
||||
if (owningStation == null || ev.Session is not IPlayerSession sessionCast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CloseEui(owningStation.Value, sessionCast, uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the crew manifest for a given station, along with the name of the station.
|
||||
/// </summary>
|
||||
/// <param name="station">Entity uid of the station.</param>
|
||||
/// <returns>The name and crew manifest entries (unordered) of the station.</returns>
|
||||
public (string name, CrewManifestEntries? entries) GetCrewManifest(EntityUid station)
|
||||
{
|
||||
var valid = _cachedEntries.TryGetValue(station, out var manifest);
|
||||
return (valid ? MetaData(station).EntityName : string.Empty, valid ? manifest : null);
|
||||
}
|
||||
|
||||
private void UpdateEuis(EntityUid station)
|
||||
{
|
||||
if (_openEuis.TryGetValue(station, out var euis))
|
||||
{
|
||||
foreach (var eui in euis.Values)
|
||||
{
|
||||
eui.StateDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenEuiFromBui(EntityUid uid, CrewManifestViewerComponent component, CrewManifestOpenUiMessage msg)
|
||||
{
|
||||
var owningStation = _stationSystem.GetOwningStation(uid);
|
||||
if (owningStation == null || msg.Session is not IPlayerSession sessionCast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_configManager.GetCVar(CCVars.CrewManifestUnsecure) && component.Unsecure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OpenEui(owningStation.Value, sessionCast, uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a crew manifest EUI for a given player.
|
||||
/// </summary>
|
||||
/// <param name="station">Station that we're displaying the crew manifest for.</param>
|
||||
/// <param name="session">The player's session.</param>
|
||||
/// <param name="owner">If this EUI should be 'owned' by an entity.</param>
|
||||
public void OpenEui(EntityUid station, IPlayerSession session, EntityUid? owner = null)
|
||||
{
|
||||
if (!HasComp<StationRecordsComponent>(station))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_openEuis.TryGetValue(station, out var euis))
|
||||
{
|
||||
euis = new();
|
||||
_openEuis.Add(station, euis);
|
||||
}
|
||||
|
||||
if (euis.ContainsKey(session))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var eui = new CrewManifestEui(station, owner, this);
|
||||
euis.Add(session, eui);
|
||||
|
||||
_euiManager.OpenEui(eui, session);
|
||||
eui.StateDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes an EUI for a given player.
|
||||
/// </summary>
|
||||
/// <param name="station">Station that we're displaying the crew manifest for.</param>
|
||||
/// <param name="session">The player's session.</param>
|
||||
/// <param name="owner">The owner of this EUI, if there was one.</param>
|
||||
public void CloseEui(EntityUid station, IPlayerSession session, EntityUid? owner = null)
|
||||
{
|
||||
if (!HasComp<StationRecordsComponent>(station))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_openEuis.TryGetValue(station, out var euis)
|
||||
|| !euis.TryGetValue(session, out var eui))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (eui.Owner == owner)
|
||||
{
|
||||
eui.Close();
|
||||
euis.Remove(session);
|
||||
}
|
||||
|
||||
if (euis.Count == 0)
|
||||
{
|
||||
_openEuis.Remove(station);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the crew manifest for a station. Stores it in the cache afterwards.
|
||||
/// </summary>
|
||||
/// <param name="station"></param>
|
||||
private void BuildCrewManifest(EntityUid station)
|
||||
{
|
||||
var iter = _recordsSystem.GetRecordsOfType<GeneralStationRecord>(station);
|
||||
|
||||
var entries = new CrewManifestEntries();
|
||||
|
||||
foreach (var recordObject in iter)
|
||||
{
|
||||
var record = recordObject.Item2;
|
||||
var entry = new CrewManifestEntry(record.Name, record.JobTitle, record.JobIcon, record.JobPrototype);
|
||||
|
||||
entries.Entries.Add(entry);
|
||||
}
|
||||
|
||||
if (_cachedEntries.ContainsKey(station))
|
||||
{
|
||||
_cachedEntries[station] = entries;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cachedEntries.Add(station, entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class CrewManifestCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "crewmanifest";
|
||||
public string Description => "Opens the crew manifest for the given station.";
|
||||
public string Help => $"Usage: {Command} <entity uid>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine($"Invalid argument count.\n{Help}");
|
||||
return;
|
||||
}
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
if (!EntityUid.TryParse(args[0], out var uid))
|
||||
{
|
||||
shell.WriteLine($"{args[0]} is not a valid entity UID.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell.Player == null || shell.Player is not IPlayerSession session)
|
||||
{
|
||||
shell.WriteLine("You must run this from a client.");
|
||||
return;
|
||||
}
|
||||
|
||||
var crewManifestSystem = entMan.EntitySysManager.GetEntitySystem<CrewManifestSystem>();
|
||||
|
||||
crewManifestSystem.OpenEui(uid, session);
|
||||
}
|
||||
}
|
||||
12
Content.Server/CrewManifest/CrewManifestViewerComponent.cs
Normal file
12
Content.Server/CrewManifest/CrewManifestViewerComponent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Content.Server.CrewManifest;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class CrewManifestViewerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// If this manifest viewer is unsecure or not. If it is,
|
||||
/// CCVars.CrewManifestUnsecure being false will
|
||||
/// not allow this entity to be processed by CrewManifestSystem.
|
||||
/// </summary>
|
||||
[DataField("unsecure")] public bool Unsecure;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Server.StationRecords;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class GeneralStationRecordConsoleComponent : Component
|
||||
{
|
||||
public StationRecordKey? ActiveKey { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Server.StationRecords;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class StationRecordKeyStorageComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The key stored in this component.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public StationRecordKey? Key;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Content.Server.StationRecords;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class StationRecordsComponent : Component
|
||||
{
|
||||
// Every single record in this station, by key.
|
||||
// Essentially a columnar database, but I really suck
|
||||
// at implementing that so
|
||||
[ViewVariables]
|
||||
public StationRecordSet Records = new();
|
||||
}
|
||||
158
Content.Server/StationRecords/StationRecordSet.cs
Normal file
158
Content.Server/StationRecords/StationRecordSet.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Server.StationRecords;
|
||||
|
||||
/// <summary>
|
||||
/// Set of station records. StationRecordsComponent stores these.
|
||||
/// Keyed by StationRecordKey, which should be obtained from
|
||||
/// an entity that stores a reference to it.
|
||||
/// </summary>
|
||||
public sealed class StationRecordSet
|
||||
{
|
||||
private uint _currentRecordId;
|
||||
|
||||
private HashSet<StationRecordKey> _keys = new();
|
||||
|
||||
private HashSet<StationRecordKey> _recentlyAccessed = new();
|
||||
|
||||
[ViewVariables]
|
||||
private Dictionary<Type, Dictionary<StationRecordKey, 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>()
|
||||
{
|
||||
if (!_tables.ContainsKey(typeof(T)))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var (key, entry) in _tables[typeof(T)])
|
||||
{
|
||||
if (entry is not T cast)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_recentlyAccessed.Add(key);
|
||||
|
||||
yield return (key, cast);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new record into this set of entries.
|
||||
/// </summary>
|
||||
/// <param name="station">Station that we're adding the record for.</param>
|
||||
/// <returns>A key that represents the record in this set.</returns>
|
||||
public StationRecordKey AddRecord(EntityUid station)
|
||||
{
|
||||
var key = new StationRecordKey(_currentRecordId++, station);
|
||||
|
||||
_keys.Add(key);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an entry into a record.
|
||||
/// </summary>
|
||||
/// <param name="key">Key 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)
|
||||
{
|
||||
if (!_keys.Contains(key) || entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_tables.TryGetValue(typeof(T), out var table))
|
||||
{
|
||||
table = new();
|
||||
_tables.Add(typeof(T), table);
|
||||
}
|
||||
|
||||
table.Add(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="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)
|
||||
{
|
||||
entry = default;
|
||||
|
||||
if (!_keys.Contains(key)
|
||||
|| !_tables.TryGetValue(typeof(T), out var table)
|
||||
|| !table.TryGetValue(key, out var entryObject))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
entry = (T) entryObject;
|
||||
_recentlyAccessed.Add(key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the record associated with this key has an entry of a certain type.
|
||||
/// </summary>
|
||||
/// <param name="key">The record key.</param>
|
||||
/// <typeparam name="T">Type to check.</typeparam>
|
||||
/// <returns>True if the entry exists, false otherwise.</returns>
|
||||
public bool HasRecordEntry<T>(StationRecordKey key)
|
||||
{
|
||||
return _keys.Contains(key)
|
||||
&& _tables.TryGetValue(typeof(T), out var table)
|
||||
&& table.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the recently accessed keys from this record set.
|
||||
/// </summary>
|
||||
/// <returns>All recently accessed keys from this record set.</returns>
|
||||
public IEnumerable<StationRecordKey> GetRecentlyAccessed()
|
||||
{
|
||||
return _recentlyAccessed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the recently accessed keys from the set.
|
||||
/// </summary>
|
||||
public void ClearRecentlyAccessed()
|
||||
{
|
||||
_recentlyAccessed.Clear();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (!_keys.Remove(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var table in _tables.Values)
|
||||
{
|
||||
table.Remove(key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
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!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, BoundUIOpenedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, SelectGeneralStationRecord>(OnKeySelected);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, RecordModifiedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<GeneralStationRecordConsoleComponent, AfterGeneralRecordCreatedEvent>(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 UpdateUserInterface(EntityUid uid, GeneralStationRecordConsoleComponent? console = null)
|
||||
{
|
||||
if (!Resolve(uid, ref console))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var owningStation = _stationSystem.GetOwningStation(uid);
|
||||
|
||||
|
||||
|
||||
if (!TryComp<StationRecordsComponent>(owningStation, out var stationRecordsComponent))
|
||||
{
|
||||
_userInterface.GetUiOrNull(uid, GeneralStationRecordConsoleKey.Key)?.SetState(new GeneralStationRecordConsoleState(null, null, null));
|
||||
return;
|
||||
}
|
||||
|
||||
var enumerator = _stationRecordsSystem.GetRecordsOfType<GeneralStationRecord>(owningStation.Value, stationRecordsComponent);
|
||||
|
||||
var listing = new Dictionary<StationRecordKey, string>();
|
||||
foreach (var pair in enumerator)
|
||||
{
|
||||
listing.Add(pair.Item1, pair.Item2.Name);
|
||||
}
|
||||
|
||||
if (listing.Count == 0)
|
||||
{
|
||||
_userInterface.GetUiOrNull(uid, GeneralStationRecordConsoleKey.Key)?.SetState(new GeneralStationRecordConsoleState(null, null, null));
|
||||
return;
|
||||
}
|
||||
|
||||
GeneralStationRecord? record = null;
|
||||
if (console.ActiveKey != null)
|
||||
{
|
||||
_stationRecordsSystem.TryGetRecord(owningStation.Value, console.ActiveKey.Value, out record,
|
||||
stationRecordsComponent);
|
||||
}
|
||||
|
||||
_userInterface
|
||||
.GetUiOrNull(uid, GeneralStationRecordConsoleKey.Key)?
|
||||
.SetState(new GeneralStationRecordConsoleState(console.ActiveKey, record, listing));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Content.Shared.StationRecords;
|
||||
|
||||
namespace Content.Server.StationRecords.Systems;
|
||||
|
||||
public sealed class StationRecordKeyStorageSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Assigns a station record key to an entity.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="keyStorage"></param>
|
||||
public void AssignKey(EntityUid uid, StationRecordKey key, StationRecordKeyStorageComponent? keyStorage = null)
|
||||
{
|
||||
if (!Resolve(uid, ref keyStorage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
keyStorage.Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a station record key from an entity.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="keyStorage"></param>
|
||||
/// <returns></returns>
|
||||
public StationRecordKey? RemoveKey(EntityUid uid, StationRecordKeyStorageComponent? keyStorage = null)
|
||||
{
|
||||
if (!Resolve(uid, ref keyStorage) || keyStorage.Key == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = keyStorage.Key;
|
||||
keyStorage.Key = null;
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an entity currently contains a station record key.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="keyStorage"></param>
|
||||
/// <returns></returns>
|
||||
public bool CheckKey(EntityUid uid, StationRecordKeyStorageComponent? keyStorage = null)
|
||||
{
|
||||
if (!Resolve(uid, ref keyStorage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return keyStorage.Key != null;
|
||||
}
|
||||
}
|
||||
282
Content.Server/StationRecords/Systems/StationRecordsSystem.cs
Normal file
282
Content.Server/StationRecords/Systems/StationRecordsSystem.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public sealed class StationRecordsSystem : EntitySystem
|
||||
{
|
||||
[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<StationInitializedEvent>(OnStationInitialize);
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn);
|
||||
}
|
||||
|
||||
private void OnStationInitialize(StationInitializedEvent args)
|
||||
{
|
||||
AddComp<StationRecordsComponent>(args.Station);
|
||||
}
|
||||
|
||||
private void OnPlayerSpawn(PlayerSpawnCompleteEvent args)
|
||||
{
|
||||
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<JobPrototype>(jobId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_inventorySystem.TryGetSlotEntity(player, "id", out var idUid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CreateGeneralRecord(station, idUid.Value, profile.Name, profile.Age, profile.Species, profile.Gender, jobId, profile, records);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a general record to store in a station's record set.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <param name="station">The entity uid of the station.</param>
|
||||
/// <param name="idUid">The entity uid of an entity's ID card. Can be null.</param>
|
||||
/// <param name="name">Name of the character.</param>
|
||||
/// <param name="species">Species of the character.</param>
|
||||
/// <param name="gender">Gender of the character.</param>
|
||||
/// <param name="jobId">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <param name="profile">
|
||||
/// 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.
|
||||
/// </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, 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,
|
||||
Age = age,
|
||||
JobTitle = jobPrototype.Name,
|
||||
JobIcon = jobPrototype.Icon,
|
||||
JobPrototype = jobId,
|
||||
Species = species,
|
||||
Gender = gender,
|
||||
DisplayPriority = jobPrototype.Weight
|
||||
};
|
||||
|
||||
var key = records.Records.AddRecord(station);
|
||||
records.Records.AddRecordEntry(key, record);
|
||||
// entry.Entries.Add(typeof(GeneralStationRecord), record);
|
||||
|
||||
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(key, record, profile));
|
||||
}
|
||||
|
||||
/// <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="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)
|
||||
{
|
||||
if (station != key.OriginStation || !Resolve(station, ref records))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RaiseLocalEvent(new RecordRemovedEvent(key));
|
||||
|
||||
return records.Records.RemoveAllRecords(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </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="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)
|
||||
{
|
||||
entry = default;
|
||||
|
||||
if (key.OriginStation != station || !Resolve(station, ref records))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return records.Records.TryGetRecordEntry(key, out entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all records of a specific type from a station.
|
||||
/// </summary>
|
||||
/// <param name="station">The station to get the records from.</param>
|
||||
/// <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)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
{
|
||||
return new (StationRecordKey, T)[]{};
|
||||
}
|
||||
|
||||
return records.Records.GetRecordsOfType<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes a station's records with any systems that need it.
|
||||
/// </summary>
|
||||
/// <param name="station">The station to synchronize any recently accessed records with..</param>
|
||||
/// <param name="records">Station records component.</param>
|
||||
public void Synchronize(EntityUid station, StationRecordsComponent? records = null)
|
||||
{
|
||||
if (!Resolve(station, ref records))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var key in records.Records.GetRecentlyAccessed())
|
||||
{
|
||||
RaiseLocalEvent(new RecordModifiedEvent(key));
|
||||
}
|
||||
|
||||
records.Records.ClearRecentlyAccessed();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public sealed class AfterGeneralRecordCreatedEvent : EntityEventArgs
|
||||
{
|
||||
public StationRecordKey Key { get; }
|
||||
public GeneralStationRecord Record { get; }
|
||||
/// <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 AfterGeneralRecordCreatedEvent(StationRecordKey key, GeneralStationRecord record, HumanoidCharacterProfile? profile)
|
||||
{
|
||||
Key = key;
|
||||
Record = record;
|
||||
Profile = profile;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public sealed class RecordRemovedEvent : EntityEventArgs
|
||||
{
|
||||
public StationRecordKey Key { get; }
|
||||
|
||||
public RecordRemovedEvent(StationRecordKey key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised after a record is modified. This is to
|
||||
/// inform other systems that records stored in this key
|
||||
/// may have changed.
|
||||
/// </summary>
|
||||
public sealed class RecordModifiedEvent : EntityEventArgs
|
||||
{
|
||||
public StationRecordKey Key { get; }
|
||||
|
||||
public RecordModifiedEvent(StationRecordKey key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
@@ -955,6 +955,37 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<string> CentcommMap =
|
||||
CVarDef.Create("shuttle.centcomm_map", "/Maps/centcomm.yml", CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* Crew Manifests
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Setting this allows a crew manifest to be opened from any window
|
||||
/// that has a crew manifest button, and sends the correct message.
|
||||
/// If this is false, only in-game entities will allow you to see
|
||||
/// the crew manifest, if the functionality is coded in.
|
||||
/// Having administrator priveledge ignores this, but will still
|
||||
/// hide the button in UI windows.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> CrewManifestWithoutEntity =
|
||||
CVarDef.Create("crewmanifest.no_entity", true, CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Setting this allows the crew manifest to be viewed from 'unsecure'
|
||||
/// entities, such as the PDA.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> CrewManifestUnsecure =
|
||||
CVarDef.Create("crewmanifest.unsecure", true, CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Dictates the order the crew manifest will appear in, in terms of its sections.
|
||||
/// Sections not in this list will appear at the end of the list, in no
|
||||
/// specific order.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> CrewManifestOrdering =
|
||||
CVarDef.Create("crewmanifest.ordering", "Command,Security,Science,Medical,Engineering,Cargo,Civilian,Unknown",
|
||||
CVar.REPLICATED);
|
||||
|
||||
/*
|
||||
* VIEWPORT
|
||||
*/
|
||||
|
||||
75
Content.Shared/CrewManifest/SharedCrewManifestSystem.cs
Normal file
75
Content.Shared/CrewManifest/SharedCrewManifestSystem.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.CrewManifest;
|
||||
|
||||
/// <summary>
|
||||
/// A message to send to the server when requesting a crew manifest.
|
||||
/// CrewManifestSystem will open an EUI that will send the crew manifest
|
||||
/// to the player when it is updated.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RequestCrewManifestMessage : EntityEventArgs
|
||||
{
|
||||
public EntityUid Id { get; }
|
||||
|
||||
public RequestCrewManifestMessage(EntityUid id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CrewManifestEuiState : EuiStateBase
|
||||
{
|
||||
public string StationName { get; }
|
||||
public CrewManifestEntries? Entries { get; }
|
||||
|
||||
public CrewManifestEuiState(string stationName, CrewManifestEntries? entries)
|
||||
{
|
||||
StationName = stationName;
|
||||
Entries = entries;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CrewManifestEuiClosed : EuiMessageBase
|
||||
{}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CrewManifestEntries
|
||||
{
|
||||
/// <summary>
|
||||
/// Entries in the crew manifest. Goes by department ID.
|
||||
/// </summary>
|
||||
// public Dictionary<string, List<CrewManifestEntry>> Entries = new();
|
||||
public List<CrewManifestEntry> Entries = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CrewManifestEntry
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public string JobTitle { get; }
|
||||
|
||||
public string JobIcon { get; }
|
||||
|
||||
public string JobPrototype { get; }
|
||||
|
||||
public CrewManifestEntry(string name, string jobTitle, string jobIcon, string jobPrototype)
|
||||
{
|
||||
Name = name;
|
||||
JobTitle = jobTitle;
|
||||
JobIcon = jobIcon;
|
||||
JobPrototype = jobPrototype;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells the server to open a crew manifest UI from
|
||||
/// this entity's point of view.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CrewManifestOpenUiMessage : BoundUserInterfaceMessage
|
||||
{}
|
||||
59
Content.Shared/StationRecords/GeneralStationRecord.cs
Normal file
59
Content.Shared/StationRecords/GeneralStationRecord.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.StationRecords;
|
||||
|
||||
/// <summary>
|
||||
/// General station record. Indicates the crewmember's name and job.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GeneralStationRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Name tied to this station record.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string Name = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Age of the person that this station record represents.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int Age;
|
||||
|
||||
/// <summary>
|
||||
/// Job title tied to this station record.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string JobTitle = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Job icon tied to this station record.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string JobIcon = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
public string JobPrototype = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Species tied to this station record.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string Species = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gender identity tied to this station record.
|
||||
/// </summary>
|
||||
/// <remarks>Sex should be placed in a medical record, not a general record.</remarks>
|
||||
[ViewVariables]
|
||||
public Gender Gender = Gender.Neuter;
|
||||
|
||||
/// <summary>
|
||||
/// The priority to display this record at.
|
||||
/// This is taken from the 'weight' of a job prototype,
|
||||
/// usually.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int DisplayPriority;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.StationRecords;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum GeneralStationRecordConsoleKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// General station records console state. There are a few states:
|
||||
/// - SelectedKey null, Record null, RecordListing null
|
||||
/// - The station record database could not be accessed.
|
||||
/// - SelectedKey null, Record null, RecordListing non-null
|
||||
/// - Records are populated in the database, or at least the station has
|
||||
/// the correct component.
|
||||
/// - SelectedKey non-null, Record null, RecordListing non-null
|
||||
/// - The selected key does not have a record tied to it.
|
||||
/// - SelectedKey non-null, Record non-null, RecordListing non-null
|
||||
/// - The selected key has a record tied to it, and the record has been sent.
|
||||
///
|
||||
/// Other states are erroneous.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GeneralStationRecordConsoleState : BoundUserInterfaceState
|
||||
{
|
||||
/// <summary>
|
||||
/// Current selected key.
|
||||
/// </summary>
|
||||
public StationRecordKey? SelectedKey { get; }
|
||||
public GeneralStationRecord? Record { get; }
|
||||
public Dictionary<StationRecordKey, string>? RecordListing { get; }
|
||||
|
||||
public GeneralStationRecordConsoleState(StationRecordKey? key, GeneralStationRecord? record, Dictionary<StationRecordKey, string>? recordListing)
|
||||
{
|
||||
SelectedKey = key;
|
||||
Record = record;
|
||||
RecordListing = recordListing;
|
||||
}
|
||||
|
||||
public bool IsEmpty() => SelectedKey == null && Record == null && RecordListing == null;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SelectGeneralStationRecord : BoundUserInterfaceMessage
|
||||
{
|
||||
public StationRecordKey? SelectedKey { get; }
|
||||
|
||||
public SelectGeneralStationRecord(StationRecordKey? selectedKey)
|
||||
{
|
||||
SelectedKey = selectedKey;
|
||||
}
|
||||
}
|
||||
21
Content.Shared/StationRecords/StationRecordKey.cs
Normal file
21
Content.Shared/StationRecords/StationRecordKey.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.StationRecords;
|
||||
|
||||
// Station record keys. These should be stored somewhere,
|
||||
// preferably within an ID card.
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct StationRecordKey
|
||||
{
|
||||
[ViewVariables]
|
||||
public uint ID { get; }
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid OriginStation { get; }
|
||||
|
||||
public StationRecordKey(uint id, EntityUid originStation)
|
||||
{
|
||||
ID = id;
|
||||
OriginStation = originStation;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ id-card-console-window-save-button = Save
|
||||
id-card-console-window-job-title-label = Job title:
|
||||
id-card-console-window-eject-button = Eject
|
||||
id-card-console-window-insert-button = Insert
|
||||
id-card-console-window-job-selection-label = Job presets:
|
||||
|
||||
access-id-card-console-component-no-hands-error = You have no hands.
|
||||
id-card-console-privileged-id = Privileged ID
|
||||
|
||||
3
Resources/Locale/en-US/crew-manifest/crew-manifest.ftl
Normal file
3
Resources/Locale/en-US/crew-manifest/crew-manifest.ftl
Normal file
@@ -0,0 +1,3 @@
|
||||
crew-manifest-window-title = Crew Manifest
|
||||
crew-manifest-button-label = Crew Manifest
|
||||
crew-manifest-no-valid-station = Invalid station, or empty manifest!
|
||||
@@ -0,0 +1,9 @@
|
||||
general-station-record-console-window-title = Station Records Computer
|
||||
general-station-record-console-select-record-info = Select a record on the left.
|
||||
general-station-record-console-empty-state = No records found!
|
||||
general-station-record-console-no-record-found = No record was found for the selected person.
|
||||
|
||||
general-station-record-console-record-age = Age: {$age}
|
||||
general-station-record-console-record-title = Job: {$job}
|
||||
general-station-record-console-record-species = Species: {$species}
|
||||
general-station-record-console-record-gender = Gender: {$gender}
|
||||
@@ -65,6 +65,8 @@
|
||||
whitelist:
|
||||
components:
|
||||
- IdCard
|
||||
- type: CrewManifestViewer
|
||||
unsecure: true
|
||||
- type: Tag
|
||||
tags:
|
||||
- DoorBumpOpener
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
heldPrefix: default
|
||||
- type: Access
|
||||
- type: IdCard
|
||||
- type: StationRecordKeyStorage
|
||||
- type: Tag
|
||||
tags:
|
||||
- DoorBumpOpener
|
||||
|
||||
@@ -183,6 +183,27 @@
|
||||
- type: Computer
|
||||
board: CriminalRecordsComputerCircuitboard
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputer
|
||||
id: ComputerStationRecords
|
||||
name: station records computer
|
||||
description: This can be used to check station records.
|
||||
components:
|
||||
- type: GeneralStationRecordConsole
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.GeneralStationRecordConsoleKey.Key
|
||||
type: GeneralStationRecordConsoleBoundUserInterface
|
||||
- type: ActivatableUI
|
||||
key: enum.GeneralStationRecordConsoleKey.Key
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: PointLight
|
||||
radius: 1.5
|
||||
energy: 1.6
|
||||
color: "#1f8c28"
|
||||
- type: Computer
|
||||
board: CriminalRecordsComputerCircuitboard
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputer
|
||||
id: ComputerCrewMonitoring
|
||||
@@ -282,6 +303,7 @@
|
||||
interfaces:
|
||||
- key: enum.IdCardConsoleUiKey.Key
|
||||
type: IdCardConsoleBoundUserInterface
|
||||
- type: CrewManifestViewer
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: ComputerVisualizer
|
||||
|
||||
Reference in New Issue
Block a user