diff --git a/.github/workflows/build-docfx.yml b/.github/workflows/build-docfx.yml index 3ef0497496..1f010b7291 100644 --- a/.github/workflows/build-docfx.yml +++ b/.github/workflows/build-docfx.yml @@ -27,7 +27,7 @@ jobs: run: dotnet restore - name: Build Project - run: dotnet build --no-restore /p:WarningsAsErrors=nullable + run: dotnet build --no-restore - name: Build DocFX uses: nikeee/docfx-action@v1.0.0 diff --git a/.github/workflows/build-map-renderer.yml b/.github/workflows/build-map-renderer.yml index 3d01618348..f93f4b25ae 100644 --- a/.github/workflows/build-map-renderer.yml +++ b/.github/workflows/build-map-renderer.yml @@ -42,7 +42,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 369239aecf..4e391b2aef 100644 --- a/.github/workflows/build-test-debug.yml +++ b/.github/workflows/build-test-debug.yml @@ -42,7 +42,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/labeler-needsreview.yml b/.github/workflows/labeler-needsreview.yml index 819b34b7bb..d3373ce91d 100644 --- a/.github/workflows/labeler-needsreview.yml +++ b/.github/workflows/labeler-needsreview.yml @@ -2,7 +2,7 @@ on: pull_request_target: - types: [review_requested] + types: [review_requested, opened] jobs: add_label: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0294395632..3ce5901841 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,6 +14,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/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 8554c97ee8..b2aeb6197a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -27,7 +27,7 @@ If you believe someone is violating the code of conduct, we ask that you report Original text courtesy of the [Speak Up! project](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html). -## On Comunity Moderation +## On Community Moderation Deviating from the Code of Conduct on the Github repository may result in moderative actions taken by project Maintainers. This can involve your content being edited or deleted, and may result in a temporary or permanent block from the repository. diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 1466614b25..83f3a35e72 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -211,7 +211,7 @@ namespace Content.Client.Actions 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 0d424cbff0..abeed65732 100644 --- a/Content.Client/Administration/AdminNameOverlay.cs +++ b/Content.Client/Administration/AdminNameOverlay.cs @@ -1,3 +1,4 @@ +using System.Collections.Frozen; using System.Linq; using System.Numerics; using Content.Client.Administration.Systems; @@ -24,6 +25,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; @@ -36,8 +38,9 @@ internal sealed class AdminNameOverlay : Overlay private float _overlayMergeDistance; //TODO make this adjustable via GUI? - private readonly ProtoId[] _filter = - ["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"]; + private static readonly FrozenSet> Filter = + new ProtoId[] {"SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"} + .ToFrozenSet(); private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic"); @@ -49,7 +52,8 @@ internal sealed class AdminNameOverlay : Overlay EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager, IConfigurationManager config, - SharedRoleSystem roles) + SharedRoleSystem roles, + IPrototypeManager prototypeManager) { _system = system; _entityManager = entityManager; @@ -57,6 +61,7 @@ internal sealed class AdminNameOverlay : Overlay _entityLookup = entityLookup; _userInterfaceManager = userInterfaceManager; _roles = roles; + _prototypeManager = prototypeManager; ZIndex = 200; // Setting these to a specific ttf would break the antag symbols _font = resourceCache.NotoStack(); @@ -125,6 +130,14 @@ internal sealed class AdminNameOverlay : Overlay 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; @@ -209,7 +222,7 @@ internal sealed class AdminNameOverlay : Overlay switch (_overlaySymbolStyle) { case AdminOverlayAntagSymbolStyle.Specific: - symbol = playerInfo.RoleProto.Symbol; + symbol = roleSymbol; break; case AdminOverlayAntagSymbolStyle.Basic: symbol = Loc.GetString("player-tab-antag-prefix"); @@ -225,17 +238,17 @@ internal sealed class AdminNameOverlay : Overlay 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; default: @@ -258,4 +271,12 @@ internal sealed class AdminNameOverlay : Overlay 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 a630df4521..e000bdc0ba 100644 --- a/Content.Client/Administration/Systems/AdminSystem.Overlay.cs +++ b/Content.Client/Administration/Systems/AdminSystem.Overlay.cs @@ -4,6 +4,7 @@ using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; namespace Content.Client.Administration.Systems { @@ -17,6 +18,7 @@ namespace Content.Client.Administration.Systems [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!; @@ -33,7 +35,8 @@ namespace Content.Client.Administration.Systems _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 333184f1c0..ede0ad3ee5 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml @@ -1,7 +1,7 @@ + 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 3c7322d473..46090a6f3d 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs @@ -1,12 +1,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; @@ -31,14 +33,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 { @@ -144,47 +153,90 @@ public sealed partial class BanPanel : DefaultWindow 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) { - foreach (var child in innerContainer.Children) + AddRoleCheckbox(groupName, role.ID, innerContainer, roleGroupCheckbox); + } + + outerContainer.AddChild(innerContainer); + + RolesContainer.AddChild(new PanelContainer + { + 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) @@ -199,15 +251,12 @@ public sealed partial class BanPanel : DefaultWindow } 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; } } @@ -220,38 +269,72 @@ public sealed partial class BanPanel : DefaultWindow 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) @@ -469,7 +552,13 @@ public sealed partial class BanPanel : DefaultWindow 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 fc4d3ee3ac..cd93ffeb0a 100644 --- a/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml +++ b/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml @@ -49,6 +49,7 @@