Skip to content

Commit 60f3c6c

Browse files
authored
Manager of Assistants - Part 2 & ID card computer QOL (#5546)
## Short description <!-- What do you propose to change with your PR? --> Part 2 of #5528, as requested in #4183. This PR purely implements the Assistant Manager's task master digi-board and the code required to make it work. The job configuration, clothing sprites, and loadout are all in part 1. This PR does (4) things to provide implementation for the Assistant Manager: - Repurposes the Agent ID UI to allow for the changing an ID card's HUD icon without changing the appearance or access of that ID card. This functionality is utilized in `IdCardConsoleSystem` and the newly created `JobIdentityConsoleSystem`. - Introduces `JobIdentityConsoleSystem`, which is effectively an ID card computer that can change an ID card's job title and HUD icon without reading or writing access tags. This interface is accessible through a new item: the task master digi-board, which allows Assistant Managers to set the job titles and HUD icons of their subordinates for more effective task delegation. - Creates 8 new "job" icons the Assistant manager can assign through their digi-board. The digiboard also has a new sprite based off the HOP's own bureaucratic digiboard. - Introduces the ability to "tag" job icons in job.yml. Consoles will listen for icons that have a tag that matches that console's designated faction. This functionality is used to limit the HUD icons available to both the Assistant Manager and anyone with access to the ID card computer. Naturally, emagging an ID card computer will now allow its user to access every icon. The universal ID card computer bypasses this functionality and will display every job icon by default. ## Why we need to add this <!-- What is the reason for adding these changes? Please post links to Discussions as well as Bug Reports here. Please describe how this will change the game balance. --> Brief summary, reasoning explained more in depth in the original issue. Highpop/Alpha consistently struggles with shittery and idleness with a regular population of 50+ assistants as a result of limited job slots. This kind of behavior among assistants places additional strain on command/security players alongside the administrative team to maintain adequate levels of roleplay standards. By placing trusted (whitelisted) players in a position to organize and coordinate this population, assistants will hopefully actually assist the crew and contribute more meaningfully to roleplay and create less friction in the course of a round. ## Media (Video/Screenshots) <!-- If your PR contains in-game changes you must provide screenshots/videos of the changes. --> <img width="182" height="113" alt="image" src="https://github.com/user-attachments/assets/7d290478-e29a-479f-adce-f662eeb575d2" /> fig 1. task master digi-board sprites <img width="565" height="287" alt="Screenshot 2026-08-06 152301" src="https://github.com/user-attachments/assets/034320f5-b8ba-4c61-8444-d6be98c1b241" /> fig 2. Assistant "Job" icons (from left to right): shopkeeper, delivery boy, gardener, cook assistant, handyman, clerical assistant, religious assistant, medical assistant. <img width="415" height="283" alt="image" src="https://github.com/user-attachments/assets/8862cbad-e283-4b1c-8bd6-0a6479a1176b" /> fig 3. the task master digi-board <img width="657" height="789" alt="Screenshot 2026-08-06 130642" src="https://github.com/user-attachments/assets/28b45304-9394-414b-b5d6-a806acac2a9b" /> fig 4. the ID card computer <img width="661" height="588" alt="Screenshot 2026-08-06 154250" src="https://github.com/user-attachments/assets/2321bab9-8855-48e0-b7b8-e8078c4c7a85" /> fig 5. an emagged ID card computer ## Checks <!-- check boxes for faster reviewing of your PR --> - [x] I do not require assistance to complete the PR. - [x] Before posting/requesting review of a PR, I have verified that the changes work. - [x] I have added screenshots/videos of the changes, or this PR does not change in-game mechanics. - [x] I affirm that my changes are licensed under the [MIT License](https://github.com/ss14Starlight/space-station-14/blob/Starlight/LICENSE.TXT) and grant permission for use in this repository under its conditions. **Changelog** :cl: bynddark8 - add: Introduces the Assistant Manager's task master digi-board, which allows them to change the job titles and HUD icons of their yellow gloved underlings. - tweak: Adds the task master digi-board to the Assistant Manager's loadout. - add: Adds 8 new sprites for informal "job" icons that can be assigned by the Assistant Manager. - add: Introduces the ability to the HUD icon associated with an ID card without changing its access on an ID card computer. - add: Emagging an ID card computer will allow its user to set their HUD icons to non-corporate approved imagery.
1 parent 829dfd5 commit 60f3c6c

38 files changed

Lines changed: 1182 additions & 27 deletions

Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
using Content.Shared.Containers.ItemSlots;
66
using Content.Shared.CrewManifest;
77
using Content.Shared.Roles;
8+
using Content.Shared.StatusIcon;
89
using Robust.Shared.Configuration;
910
using Robust.Shared.Prototypes;
1011
using static Content.Shared.Access.Components.IdCardConsoleComponent;
11-
// Starlight-edit: Start
1212
using Robust.Client.UserInterface;
1313
using Content.Shared._Starlight.Access;
14-
// Starlight-edit: End
1514

1615
namespace Content.Client.Access.UI
1716
{
@@ -39,8 +38,10 @@ protected override void Open()
3938
{
4039
base.Open();
4140
// Starlight-edit: Start
41+
var requiredTags = EntMan.GetComponent<IdCardConsoleComponent>(Owner).RequiredTags;
42+
4243
_window = this.CreateWindow<IdCardConsoleWindow>();
43-
_window.Initialize(this);
44+
_window.Initialize(this, requiredTags);
4445
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
4546
// Starlight-edit: End
4647
_window.CrewManifestButton.OnPressed += _ => SendMessage(new CrewManifestOpenUiMessage());
@@ -66,8 +67,8 @@ protected override void UpdateState(BoundUserInterfaceState state)
6667
var castState = (IdCardConsoleBoundUserInterfaceState) state;
6768
_window?.UpdateState(castState);
6869
}
69-
70-
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, ProtoId<JobPrototype>? newJobPrototype) // Starlight: Nullable newJobPrototype
70+
// Starlight-edit: Start
71+
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, ProtoId<JobPrototype>? newJobPrototype, ProtoId<JobIconPrototype>? newJobIcon) // Starlight: Nullable newJobPrototype, newJobIcon
7172
{
7273
if (newFullName.Length > _maxNameLength)
7374
newFullName = newFullName[.._maxNameLength];
@@ -79,14 +80,14 @@ public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<Acce
7980
newFullName,
8081
newJobTitle,
8182
newAccessList,
82-
newJobPrototype));
83+
newJobPrototype,
84+
newJobIcon));
8385
}
84-
// Starlight-edit: Start
86+
// Starlight-edit: End
8587

8688
public void OnGroupSelected(ProtoId<AccessGroupPrototype> group)
8789
{
8890
SendMessage(new IdCardConsoleComponent.AccessGroupSelectedMessage(group)); // Starlight-edit
8991
}
90-
// Starlight-edit: End
9192
}
9293
}

Content.Client/Access/UI/IdCardConsoleWindow.xaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<DefaultWindow xmlns="https://spacestation14.io"
22
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
3-
MinSize="650 290">
3+
MinSize="650 340">
44
<BoxContainer Orientation="Vertical">
55
<!-- Privileged and target IDs, crew manifest button. -->
66
<GridContainer Columns="2">
@@ -29,6 +29,12 @@
2929
<Button Name="JobTitleSaveButton" Text="{Loc 'id-card-console-window-save-button'}" Disabled="True" />
3030
</GridContainer>
3131
<Control MinSize="0 8" />
32+
<!-- Starlight-start: Job icon selection -->
33+
<Label Name="JobIconLabel" Text="{Loc 'agent-id-card-job-icon-label'}"/>
34+
<GridContainer Name="IconGrid" Columns="10">
35+
</GridContainer>
36+
<Control MinSize="0 8" />
37+
<!-- Starlight-end -->
3238
<!-- Job preset selection, grant/revoke all access buttons. -->
3339
<BoxContainer Margin="0 8 0 4">
3440
<BoxContainer>

Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
using System.Linq;
2+
using System.Numerics;
23
using Content.Client._Starlight.Access.UI; // Starlight-edit
4+
using Content.Client.Stylesheets;
35
using Content.Shared.Access;
46
using Content.Shared.Access.Systems;
57
using Content.Shared.CCVar;
68
using Content.Shared.Roles;
9+
using Content.Shared.StatusIcon;
10+
using Content.Shared.Tag;
711
using Robust.Client.AutoGenerated;
12+
using Robust.Client.GameObjects;
13+
using Robust.Client.UserInterface;
814
using Robust.Client.UserInterface.Controls;
915
using Robust.Client.UserInterface.CustomControls;
1016
using Robust.Client.UserInterface.XAML;
@@ -20,21 +26,28 @@ public sealed partial class IdCardConsoleWindow : DefaultWindow
2026
[Dependency] private IConfigurationManager _cfgManager = default!;
2127
[Dependency] private IPrototypeManager _prototypeManager = default!;
2228
[Dependency] private ILogManager _logManager = default!;
29+
[Dependency] private IEntitySystemManager _entitySystem = default!; // Starlight-edit
2330
private readonly ISawmill _logMill = default!;
31+
private readonly SpriteSystem _spriteSystem; // Starlight-edit
2432

2533
private IdCardConsoleBoundUserInterface? _owner; // Starlight edit
2634

2735
// CCVar.
2836
private int _maxNameLength;
2937
private int _maxIdJobLength;
3038

39+
private const int JobIconColumnCount = 10; // Starlight-edit
40+
3141
private AccessLevelControl _accessButtons = new();
3242
private AccessGroupControl _accessGroups = new(); // Starlight-edit: Access Groups
3343
private readonly List<string> _jobPrototypeIds = new();
3444

3545
private string? _lastFullName;
3646
private string? _lastJobTitle;
3747
private string? _lastJobProto;
48+
private ProtoId<JobIconPrototype> _currentJobIcon = "JobIconUnknown"; // Starlight-edit
49+
private bool _allIconsUnlocked; // Starlight-edit
50+
private HashSet<ProtoId<TagPrototype>> _requiredTags = new(); // Starlight-edit
3851

3952
// The job that will be picked if the ID doesn't have a job on the station.
4053
private static ProtoId<JobPrototype> _defaultJob = "Assistant";
@@ -54,6 +67,7 @@ public IdCardConsoleWindow()
5467
RobustXamlLoader.Load(this);
5568
IoCManager.InjectDependencies(this);
5669
_logMill = _logManager.GetSawmill(SharedIdCardConsoleSystem.Sawmill);
70+
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>(); // Starlight-edit
5771

5872
// Starlight _owner = owner; // Starlight edit
5973

@@ -121,9 +135,10 @@ public IdCardConsoleWindow()
121135
}
122136
}
123137

124-
public void Initialize(IdCardConsoleBoundUserInterface owner)
138+
public void Initialize(IdCardConsoleBoundUserInterface owner, HashSet<ProtoId<TagPrototype>> requiredTags)
125139
{
126140
_owner = owner;
141+
_requiredTags = requiredTags;
127142

128143
OnGroupSelected += group => _owner.OnGroupSelected(group);
129144
// Starlight-edit: End
@@ -147,6 +162,12 @@ private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args)
147162
JobTitleLineEdit.Text = Loc.GetString(job.Name);
148163
args.Button.SelectId(args.Id);
149164

165+
// Starlight-edit: Start - picking a job preset will reset the id card's hud icon to that job's default.
166+
// This can be overrided by picking another icon in the UI.
167+
_currentJobIcon = job.Icon;
168+
PopulateIcons(true);
169+
// Starlight-edit: End
170+
150171
SetAllAccess(false);
151172

152173
// Collect all access levels for this job (direct + all groups)
@@ -218,6 +239,13 @@ public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
218239

219240
JobTitleSaveButton.Disabled = !interfaceEnabled || !jobTitleDirty;
220241

242+
// Starlight-edit: Start - job icon selection
243+
_currentJobIcon = state.TargetIdJobIcon ?? _currentJobIcon;
244+
_allIconsUnlocked = state.AllIconsUnlocked;
245+
JobIconLabel.Modulate = interfaceEnabled ? Color.White : Color.Gray;
246+
PopulateIcons(interfaceEnabled);
247+
// Starlight-edit: End
248+
221249
JobPresetOptionButton.Disabled = !interfaceEnabled;
222250

223251
// Starlight-edit: Start
@@ -360,6 +388,58 @@ public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
360388
_lastJobProto = state.TargetIdJobPrototype;
361389
}
362390

391+
// Starlight-edit: Start - job icon grid, based on AgentIDCardWindow/JobIdentityConsoleWindow
392+
private void PopulateIcons(bool enabled)
393+
{
394+
IconGrid.RemoveAllChildren();
395+
396+
var jobIconButtonGroup = new ButtonGroup();
397+
var i = 0;
398+
var icons = _prototypeManager.EnumeratePrototypes<JobIconPrototype>()
399+
.Where(icon => icon.AllowSelection && (_allIconsUnlocked || icon.Tags.Overlaps(_requiredTags)))
400+
.ToList();
401+
icons.Sort((x, y) => string.Compare(x.LocalizedJobName, y.LocalizedJobName, StringComparison.CurrentCulture));
402+
403+
foreach (var jobIcon in icons)
404+
{
405+
var styleBase = StyleClass.ButtonOpenBoth;
406+
var modulo = i % JobIconColumnCount;
407+
if (modulo == 0)
408+
styleBase = StyleClass.ButtonOpenRight;
409+
else if (modulo == JobIconColumnCount - 1)
410+
styleBase = StyleClass.ButtonOpenLeft;
411+
412+
var jobIconButton = new Button
413+
{
414+
Access = AccessLevel.Public,
415+
StyleClasses = { styleBase },
416+
MaxSize = new Vector2(42, 28),
417+
Group = jobIconButtonGroup,
418+
Pressed = _currentJobIcon == jobIcon.ID,
419+
Disabled = !enabled,
420+
ToolTip = jobIcon.LocalizedJobName,
421+
};
422+
423+
var jobIconTexture = new TextureRect
424+
{
425+
Texture = _spriteSystem.Frame0(jobIcon.Icon),
426+
TextureScale = new Vector2(2.5f, 2.5f),
427+
Stretch = TextureRect.StretchMode.KeepCentered,
428+
};
429+
430+
jobIconButton.AddChild(jobIconTexture);
431+
jobIconButton.OnPressed += _ =>
432+
{
433+
_currentJobIcon = jobIcon.ID;
434+
SubmitData();
435+
};
436+
IconGrid.AddChild(jobIconButton);
437+
438+
i++;
439+
}
440+
}
441+
// Starlight-edit: End
442+
363443
private void SubmitData()
364444
{
365445
// Don't send this if it isn't dirty.
@@ -371,7 +451,8 @@ private void SubmitData()
371451
FullNameLineEdit.Text,
372452
JobTitleLineEdit.Text,
373453
_pendingPressedAccessLevels.ToList(),
374-
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : null);
454+
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : null,
455+
_currentJobIcon);
375456

376457
// Clear the override after submit so next UpdateState can update pressed state as normal
377458
_pendingAccessOverride = false;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using Content.Shared._Starlight.Access.Components;
2+
using Content.Shared.CCVar;
3+
using Content.Shared.Containers.ItemSlots;
4+
using Content.Shared.StatusIcon;
5+
using Robust.Client.UserInterface;
6+
using Robust.Shared.Configuration;
7+
using Robust.Shared.Prototypes;
8+
using static Content.Shared._Starlight.Access.Components.JobIdentityConsoleComponent;
9+
10+
namespace Content.Client._Starlight.Access.UI;
11+
12+
public sealed partial class JobIdentityConsoleBoundUserInterface : BoundUserInterface
13+
{
14+
[Dependency] private IConfigurationManager _cfgManager = default!;
15+
16+
private JobIdentityConsoleWindow? _window;
17+
18+
private readonly int _maxIdJobLength;
19+
20+
public JobIdentityConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
21+
{
22+
_maxIdJobLength = _cfgManager.GetCVar(CCVars.MaxIdJobLength);
23+
}
24+
25+
protected override void Open()
26+
{
27+
base.Open();
28+
29+
var requiredTags = EntMan.GetComponent<JobIdentityConsoleComponent>(Owner).RequiredTags;
30+
31+
_window = this.CreateWindow<JobIdentityConsoleWindow>();
32+
_window.Initialize(this, requiredTags);
33+
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
34+
35+
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
36+
_window.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(TargetIdCardSlotId));
37+
38+
_window.OnClose += Close;
39+
_window.OpenCentered();
40+
}
41+
42+
protected override void Dispose(bool disposing)
43+
{
44+
base.Dispose(disposing);
45+
if (!disposing)
46+
return;
47+
48+
_window?.Dispose();
49+
}
50+
51+
protected override void UpdateState(BoundUserInterfaceState state)
52+
{
53+
base.UpdateState(state);
54+
var castState = (JobIdentityConsoleBoundUserInterfaceState) state;
55+
_window?.UpdateState(castState);
56+
}
57+
58+
public void SubmitData(string newJobTitle, ProtoId<JobIconPrototype>? newJobIcon)
59+
{
60+
if (newJobTitle.Length > _maxIdJobLength)
61+
newJobTitle = newJobTitle[.._maxIdJobLength];
62+
63+
SendMessage(new WriteJobIdentityMessage(newJobTitle, newJobIcon));
64+
}
65+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<DefaultWindow xmlns="https://spacestation14.io"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
Title="{Loc 'job-identity-console-window-title'}"
4+
MinSize="420 280">
5+
<BoxContainer Orientation="Vertical">
6+
<!-- Privileged and target IDs. -->
7+
<GridContainer Columns="3" HorizontalExpand="True">
8+
<Label Text="{Loc 'id-card-console-window-privileged-id'}" />
9+
<Button Name="PrivilegedIdButton" Access="Public"/>
10+
<Label Name="PrivilegedIdLabel" />
11+
12+
<Label Text="{Loc 'id-card-console-window-target-id'}" />
13+
<Button Name="TargetIdButton" Access="Public"/>
14+
<Label Name="TargetIdLabel" />
15+
</GridContainer>
16+
<Control MinSize="0 8" />
17+
<!-- Job title editing. -->
18+
<GridContainer Columns="3" HSeparationOverride="4">
19+
<Label Name="JobTitleLabel" Text="{Loc 'id-card-console-window-job-title-label'}" />
20+
<LineEdit Name="JobTitleLineEdit" HorizontalExpand="True" />
21+
<Button Name="JobTitleSaveButton" Text="{Loc 'id-card-console-window-save-button'}" Disabled="True" />
22+
</GridContainer>
23+
<Control MinSize="0 8" />
24+
<!-- Job icon selection. -->
25+
<Label Name="IconGridLabel" Text="{Loc 'agent-id-card-job-icon-label'}"/>
26+
<GridContainer Name="IconGrid" Columns="10">
27+
<!-- Job icon buttons are generated in code -->
28+
</GridContainer>
29+
</BoxContainer>
30+
</DefaultWindow>

0 commit comments

Comments
 (0)