diff --git a/.github/workflows/build-map-renderer.yml b/.github/workflows/build-map-renderer.yml index 564eb8a2f45..da9fc313bd1 100644 --- a/.github/workflows/build-map-renderer.yml +++ b/.github/workflows/build-map-renderer.yml @@ -61,7 +61,7 @@ jobs: run: dotnet restore - name: Build Project - run: dotnet build Content.MapRenderer --configuration Release --no-restore /p:WarningsAsErrors=nullable /m + run: dotnet build Content.MapRenderer --configuration Release --no-restore /m - name: Run Map Renderer run: dotnet run --project Content.MapRenderer Dev diff --git a/.github/workflows/build-test-debug.yml b/.github/workflows/build-test-debug.yml index 8d48744fa8a..cd5110c4845 100644 --- a/.github/workflows/build-test-debug.yml +++ b/.github/workflows/build-test-debug.yml @@ -67,7 +67,7 @@ jobs: run: dotnet restore - name: Build Project - run: dotnet build --configuration DebugOpt --no-restore /p:WarningsAsErrors=nullable /m + run: dotnet build --configuration DebugOpt --no-restore /m - name: Run Content.Tests run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0d930f06d21..ec140fae558 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,6 +29,10 @@ jobs: runs-on: ubuntu-latest steps: + - name: Fail if we are attempting to run on the master branch + if: ${{GITHUB.REF_NAME == 'master' && github.repository == 'space-wizards/space-station-14'}} + run: exit 1 + - name: Install dependencies run: sudo apt-get install -y python3-paramiko python3-lxml diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs index 1b30a589d6a..9a0118fc529 100644 --- a/Content.Benchmarks/MapLoadBenchmark.cs +++ b/Content.Benchmarks/MapLoadBenchmark.cs @@ -77,7 +77,7 @@ public async Task Cleanup() PoolManager.Shutdown(); } - public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Packed", "Origin", "Omega", "Cluster", "Reach", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Oasis", "FlandHighPop", "OasisHighPop", "OriginHighPop", "Barratry", "Kettle", "Submarine", "Lambda", "Leonid", "Delta", "Amber", "Chloris", "Cog", "Glacier"}; //Goobstation, readds maps + public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Packed", "Origin", "Omega", "Cluster", "Reach", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Oasis", "FlandHighPop", "OasisHighPop", "OriginHighPop", "Barratry", "Kettle", "Submarine", "Lambda", "Leonid", "Delta", "Amber", "Chloris", "Cog", "Serpentcrest", "Glacier"}; //Goobstation, readds maps // Omu Glacier [ParamsSource(nameof(MapsSource))] public string Map; diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index f88573bff39..b0fb8cbfae7 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -368,7 +368,7 @@ public void TriggerAction(Entity action) else { var request = new RequestPerformActionEvent(GetNetEntity(action)); - EntityManager.RaisePredictiveEvent(request); + RaisePredictiveEvent(request); } } diff --git a/Content.Client/Administration/AdminNameOverlay.cs b/Content.Client/Administration/AdminNameOverlay.cs index 6a199cd63cc..55e26765f13 100644 --- a/Content.Client/Administration/AdminNameOverlay.cs +++ b/Content.Client/Administration/AdminNameOverlay.cs @@ -102,6 +102,7 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later +using System.Collections.Frozen; using System.Linq; using System.Numerics; using Content.Client.Administration.Systems; @@ -129,6 +130,7 @@ internal sealed class AdminNameOverlay : Overlay private readonly EntityLookupSystem _entityLookup; private readonly IUserInterfaceManager _userInterfaceManager; private readonly SharedRoleSystem _roles; + private readonly IPrototypeManager _prototypeManager; private readonly Font _font; private readonly Font _fontBold; private AdminOverlayAntagFormat _overlayFormat; @@ -145,9 +147,10 @@ internal sealed class AdminNameOverlay : Overlay private bool _showUserName; // Goobstation - End - //TODO make this adjustable via GUI - private readonly ProtoId[] _filter = - ["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"]; + //TODO make this adjustable via GUI? + private static readonly FrozenSet> Filter = + new ProtoId[] {"SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"} + .ToFrozenSet(); private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic"); @@ -159,7 +162,8 @@ public AdminNameOverlay( EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager, IConfigurationManager config, - SharedRoleSystem roles) + SharedRoleSystem roles, + IPrototypeManager prototypeManager) { _system = system; _entityManager = entityManager; @@ -167,6 +171,7 @@ public AdminNameOverlay( _entityLookup = entityLookup; _userInterfaceManager = userInterfaceManager; _roles = roles; + _prototypeManager = prototypeManager; ZIndex = 200; // Setting these to a specific ttf would break the antag symbols _font = resourceCache.NotoStack(); @@ -240,6 +245,14 @@ protected override void Draw(in OverlayDrawArgs args) foreach (var info in sortable.OrderBy(s => s.Item4.Y).ToList()) { var playerInfo = info.Item1; + var rolePrototype = playerInfo.RoleProto == null + ? null + : _prototypeManager.Index(playerInfo.RoleProto.Value); + + var roleName = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName); + var roleColor = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor; + var roleSymbol = rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol; + var aabb = info.Item2; var entity = info.Item3; var screenCoordinatesCenter = info.Item4; @@ -330,7 +343,7 @@ protected override void Draw(in OverlayDrawArgs args) switch (_overlaySymbolStyle) { case AdminOverlayAntagSymbolStyle.Specific: - symbol = playerInfo.RoleProto.Symbol; + symbol = roleSymbol; break; case AdminOverlayAntagSymbolStyle.Basic: symbol = Loc.GetString("player-tab-antag-prefix"); @@ -346,21 +359,21 @@ protected override void Draw(in OverlayDrawArgs args) switch (_overlayFormat) { case AdminOverlayAntagFormat.Roletype: - color = playerInfo.RoleProto.Color; - symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty; - text = _filter.Contains(playerInfo.RoleProto) - ? Loc.GetString(playerInfo.RoleProto.Name).ToUpper() + color = roleColor; + symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty; + text = IsFiltered(playerInfo.RoleProto) + ? roleName.ToUpper() : string.Empty; break; case AdminOverlayAntagFormat.Subtype: - color = playerInfo.RoleProto.Color; - symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty; - text = _filter.Contains(playerInfo.RoleProto) - ? _roles.GetRoleSubtypeLabel(playerInfo.RoleProto.Name, playerInfo.Subtype).ToUpper() + color = roleColor; + symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty; + text = IsFiltered(playerInfo.RoleProto) + ? _roles.GetRoleSubtypeLabel(roleName, playerInfo.Subtype).ToUpper() : string.Empty; break; case AdminOverlayAntagFormat.Off: // Goobstation - color = playerInfo.RoleProto.Color; + color = roleColor; symbol = string.Empty; text = string.Empty; break; @@ -384,4 +397,12 @@ protected override void Draw(in OverlayDrawArgs args) drawnOverlays.Add((screenCoordinatesCenter, currentOffset)); } } + + private static bool IsFiltered(ProtoId? roleProtoId) + { + if (roleProtoId == null) + return false; + + return Filter.Contains(roleProtoId.Value); + } } diff --git a/Content.Client/Administration/Systems/AdminSystem.Overlay.cs b/Content.Client/Administration/Systems/AdminSystem.Overlay.cs index 1b54814cb75..6b4c50d0f23 100644 --- a/Content.Client/Administration/Systems/AdminSystem.Overlay.cs +++ b/Content.Client/Administration/Systems/AdminSystem.Overlay.cs @@ -53,6 +53,7 @@ using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; namespace Content.Client.Administration.Systems { @@ -66,6 +67,7 @@ public sealed partial class AdminSystem [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; private AdminNameOverlay _adminNameOverlay = default!; @@ -82,7 +84,8 @@ private void InitializeOverlay() _entityLookup, _userInterfaceManager, _configurationManager, - _roles); + _roles, + _proto); _adminManager.AdminStatusUpdated += OnAdminStatusUpdated; } diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml index 47386524069..2bdd08ad720 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml @@ -11,7 +11,7 @@ SPDX-License-Identifier: MIT + Title="{Loc ban-panel-title}" MinSize="410 500"> diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs index 6e5094a9d08..9f3ac553227 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs @@ -12,12 +12,14 @@ using System.Linq; using System.Net; using System.Net.Sockets; +using System.Numerics; using Content.Client.Administration.UI.CustomControls; using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Roles; using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -42,14 +44,21 @@ public sealed partial class BanPanel : DefaultWindow private uint Multiplier { get; set; } private bool HasBanFlag { get; set; } private TimeSpan? ButtonResetOn { get; set; } + // This is less efficient than just holding a reference to the root control and enumerating children, but you // have to know how the controls are nested, which makes the code more complicated. - private readonly List _roleCheckboxes = new(); + // Role group name -> the role buttons themselves. + private readonly Dictionary> _roleCheckboxes = new(); private readonly ISawmill _banpanelSawmill; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + + private const string ExpandedArrow = "▼"; + private const string ContractedArrow = "▶"; private enum TabNumbers { @@ -155,47 +164,90 @@ public BanPanel() ReasonTextEdit.Placeholder = new Rope.Leaf(Loc.GetString("ban-panel-reason")); - var prototypeManager = IoCManager.Resolve(); - foreach (var proto in prototypeManager.EnumeratePrototypes()) + var departmentJobs = _protoMan.EnumeratePrototypes() + .OrderBy(x => x.Weight); + foreach (var proto in departmentJobs) { - CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color); + var roles = proto.Roles.Select(x => _protoMan.Index(x)) + .OrderBy(x => x.ID); + CreateRoleGroup(proto.ID, proto.Color, roles); } - CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes().Select(p => p.ID), Color.Red); + var antagRoles = _protoMan.EnumeratePrototypes() + .OrderBy(x => x.ID); + CreateRoleGroup("Antagonist", Color.Red, antagRoles); } - private void CreateRoleGroup(string roleName, IEnumerable roleList, Color color) + /// + /// Creates a "Role group" which stores information and logic for one "group" of roll bans. + /// For example, all antags are one group, logi is a group, medical is a group, etc... + /// + private void CreateRoleGroup(string groupName, Color color, IEnumerable roles) where T : class, IPrototype { var outerContainer = new BoxContainer { - Name = $"{roleName}GroupOuterBox", + Name = $"{groupName}GroupOuterBox", HorizontalExpand = true, VerticalExpand = true, Orientation = BoxContainer.LayoutOrientation.Vertical, - Margin = new Thickness(4) + Margin = new Thickness(4), }; - var departmentCheckbox = new CheckBox + + // Stores stuff like ban all and expand buttons. + var roleGroupHeader = new BoxContainer { - Name = $"{roleName}GroupCheckbox", - Text = roleName, - Modulate = color, - HorizontalAlignment = HAlignment.Left + Orientation = BoxContainer.LayoutOrientation.Horizontal, }; - outerContainer.AddChild(departmentCheckbox); - var innerContainer = new BoxContainer + + // Stores the role checkboxes themselves. + var innerContainer = new GridContainer { - Name = $"{roleName}GroupInnerBox", + Name = $"{groupName}GroupInnerBox", HorizontalExpand = true, - Orientation = BoxContainer.LayoutOrientation.Horizontal + Columns = 2, + Visible = false, + Margin = new Thickness(15, 5, 0, 5), }; - departmentCheckbox.OnToggled += args => + + var roleGroupCheckbox = CreateRoleGroupHeader(groupName, roleGroupHeader, color, innerContainer); + + outerContainer.AddChild(roleGroupHeader); + + // Add the roles themselves + foreach (var role in roles) + { + AddRoleCheckbox(groupName, role.ID, innerContainer, roleGroupCheckbox); + } + + outerContainer.AddChild(innerContainer); + + RolesContainer.AddChild(new PanelContainer { - foreach (var child in innerContainer.Children) + PanelOverride = new StyleBoxFlat { - if (child is CheckBox c) - { - c.Pressed = args.Pressed; - } + BackgroundColor = color + } + }); + RolesContainer.AddChild(outerContainer); + RolesContainer.AddChild(new HSeparator()); + } + + private Button CreateRoleGroupHeader(string groupName, BoxContainer header, Color color, GridContainer innerContainer) + { + var roleGroupCheckbox = new Button + { + Name = $"{groupName}GroupCheckbox", + Text = "Ban all", + Margin = new Thickness(0, 0, 5, 0), + ToggleMode = true, + }; + + // When this is toggled, toggle all buttons in this group so they match. + roleGroupCheckbox.OnToggled += args => + { + foreach (var role in _roleCheckboxes[groupName]) + { + role.Pressed = args.Pressed; } if (args.Pressed) @@ -210,15 +262,12 @@ private void CreateRoleGroup(string roleName, IEnumerable roleList, Colo } else { - foreach (var childContainer in RolesContainer.Children) + foreach (var roleButtons in _roleCheckboxes.Values) { - if (childContainer is Container) + foreach (var button in roleButtons) { - foreach (var child in childContainer.Children) - { - if (child is CheckBox { Pressed: true }) - return; - } + if (button.Pressed) + return; } } @@ -231,38 +280,72 @@ private void CreateRoleGroup(string roleName, IEnumerable roleList, Colo SeverityOption.SelectId((int) newSeverity); } }; - outerContainer.AddChild(innerContainer); - foreach (var role in roleList) + + var hideButton = new Button { - AddRoleCheckbox(role, innerContainer, departmentCheckbox); - } - RolesContainer.AddChild(new PanelContainer + Text = Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow, + ToggleMode = true, + }; + hideButton.OnPressed += args => { - PanelOverride = new StyleBoxFlat - { - BackgroundColor = color - } + innerContainer.Visible = args.Button.Pressed; + ((Button)args.Button).Text = args.Button.Pressed + ? Loc.GetString("role-bans-contract-roles") + " " + ExpandedArrow + : Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow; + }; + header.AddChild(new Label + { + Text = groupName, + Modulate = color, + Margin = new Thickness(0, 0, 5, 0), }); - RolesContainer.AddChild(outerContainer); - RolesContainer.AddChild(new HSeparator()); + header.AddChild(roleGroupCheckbox); + header.AddChild(hideButton); + return roleGroupCheckbox; } - private void AddRoleCheckbox(string role, Control container, CheckBox header) + /// + /// Adds a checkbutton specifically for one "role" in a "group" + /// E.g. it would add the Chief Medical Officer "role" into the "Medical" group. + /// + private void AddRoleCheckbox(string group, string role, GridContainer roleGroupInnerContainer, Button roleGroupCheckbox) { - var roleCheckbox = new CheckBox + var roleCheckboxContainer = new BoxContainer(); + var roleCheckButton = new Button { Name = $"{role}RoleCheckbox", - Text = role + Text = role, + ToggleMode = true, }; - roleCheckbox.OnToggled += args => + roleCheckButton.OnToggled += args => { - if (args is { Pressed: true, Button.Parent: { } } && args.Button.Parent.Children.Where(e => e is CheckBox).All(e => ((CheckBox) e).Pressed)) - header.Pressed = args.Pressed; + // Checks the role group checkbox if all the children are pressed + if (args.Pressed && _roleCheckboxes[group].All(e => e.Pressed)) + roleGroupCheckbox.Pressed = args.Pressed; else - header.Pressed = false; + roleGroupCheckbox.Pressed = false; }; - container.AddChild(roleCheckbox); - _roleCheckboxes.Add(roleCheckbox); + + // This is adding the icon before the role name + // Yeah, this is sus, but having to split the functions up and stuff is worse imo. + if (_protoMan.TryIndex(role, out var jobPrototype) && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto)) + { + var jobIconTexture = new TextureRect + { + Texture = _entMan.System().Frame0(iconProto.Icon), + TextureScale = new Vector2(2.5f, 2.5f), + Stretch = TextureRect.StretchMode.KeepCentered, + Margin = new Thickness(5, 0, 0, 0), + }; + roleCheckboxContainer.AddChild(jobIconTexture); + } + + roleCheckboxContainer.AddChild(roleCheckButton); + + roleGroupInnerContainer.AddChild(roleCheckboxContainer); + + _roleCheckboxes.TryAdd(group, []); + _roleCheckboxes[group].Add(roleCheckButton); } public void UpdateBanFlag(bool newFlag) @@ -480,7 +563,13 @@ private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj) if (_roleCheckboxes.Count == 0) throw new DebugAssertException("RoleCheckboxes was empty"); - rolesList.AddRange(_roleCheckboxes.Where(c => c is { Pressed: true, Text: { } }).Select(c => c.Text!)); + foreach (var button in _roleCheckboxes.Values.SelectMany(departmentButtons => departmentButtons)) + { + if (button is { Pressed: true, Text: not null }) + { + rolesList.Add(button.Text); + } + } if (rolesList.Count == 0) { diff --git a/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml b/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml index 753c0473b7a..a95dc6cddf9 100644 --- a/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml +++ b/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml @@ -61,6 +61,7 @@ SPDX-License-Identifier: MIT