Station records (#8720)

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Flipp Syder
2022-08-08 22:10:01 -07:00
committed by GitHub
parent 75dfbdb57f
commit 3d36a6e1f6
35 changed files with 1888 additions and 9 deletions

View File

@@ -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));

View File

@@ -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 -->

View File

@@ -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;

View 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);
}
}

View 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;
}
}

View 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>

View 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);
}
}
}
}

View File

@@ -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,

View File

@@ -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));

View File

@@ -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'}" />

View File

@@ -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();
}
}

View File

@@ -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>

View File

@@ -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);
}
}
}