diff --git a/.editorconfig b/.editorconfig index 8b92144453..8a5cf43521 100644 --- a/.editorconfig +++ b/.editorconfig @@ -127,6 +127,7 @@ csharp_indent_braces = false #csharp_indent_case_contents_when_block = true #csharp_indent_labels = one_less_than_current csharp_indent_switch_labels = true +xmldoc_indent_text = zeroindent # Space preferences csharp_space_after_cast = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 170b8eadce..3ced536747 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,8 +8,8 @@ *.ftl @ficcialfaint # Map files -/Resources/Prototypes/Maps/** @Ko4ergaPunk -/Resources/Maps/** @Ko4ergaPunk +/Resources/Prototypes/Maps/** @Ko4ergaPunk +/Resources/Maps/** @Ko4ergaPunk # Sprites -/Resources/Textures/** @SonicHDC +/Resources/Textures/** @SonicHDC diff --git a/.github/workflows/publish-testing.yml b/.github/workflows/publish-testing.yml index aa3b35dea1..6dacef1324 100644 --- a/.github/workflows/publish-testing.yml +++ b/.github/workflows/publish-testing.yml @@ -2,6 +2,7 @@ concurrency: group: publish-testing + cancel-in-progress: true on: workflow_dispatch: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f57e1f3e70..3ef3dabaab 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,6 +2,7 @@ name: Publish concurrency: group: publish + cancel-in-progress: true on: workflow_dispatch: @@ -66,12 +67,14 @@ jobs: FORK_ID: ${{ vars.FORK_ID }} # - name: Publish changelog (Discord) + # continue-on-error: true # run: Tools/actions_changelogs_since_last_run.py # env: # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }} # - name: Publish changelog (RSS) + # continue-on-error: true # run: Tools/actions_changelog_rss.py # env: # CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 1e95fb41b0..7f4efc8c9a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -32,6 +32,50 @@ "/consoleloggerparameters:'ForceNoAlign;NoSummary'" ], "problemMatcher": "$msCompile" + }, + { + "label": "test", + "command": "dotnet", + "type": "shell", + "args": [ + "test", + "--no-build", + "--configuration", + "DebugOpt", + "Content.Tests/Content.Tests.csproj", + "--", + "NUnit.ConsoleOut=0" + ], + "group": { + "kind": "test" + }, + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$msCompile" + }, + { + "label": "integration-test", + "command": "dotnet", + "type": "shell", + "args": [ + "test", + "--no-build", + "--configuration", + "DebugOpt", + "Content.IntegrationTests/Content.IntegrationTests.csproj", + "--", + "NUnit.ConsoleOut=0", + "NUnit.MapWarningTo=Failed.ConsoleOut=0", + "NUnit.MapWarningTo=Failed" + ], + "group": { + "kind": "test" + }, + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$msCompile" } ] } diff --git a/BuildChecker/git_helper.py b/BuildChecker/git_helper.py index becd4506e8..96a7bdae2a 100644 --- a/BuildChecker/git_helper.py +++ b/BuildChecker/git_helper.py @@ -5,6 +5,7 @@ import subprocess import sys import os import shutil +import time from pathlib import Path from typing import List @@ -104,7 +105,21 @@ def reset_solution(): with SOLUTION_PATH.open("w") as f: f.write(content) +def check_for_zip_download(): + # Check if .git exists, + cur_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + if not os.path.isdir(os.path.join(cur_dir, ".git")): + print("It appears that you downloaded this repository directly from GitHub. (Using the .zip download option) \n" + "When downloading straight from GitHub, it leaves out important information that git needs to function. " + "Such as information to download the engine or even the ability to even be able to create contributions. \n" + "Please read and follow https://docs.spacestation14.com/en/general-development/setup/setting-up-a-development-environment.html \n" + "If you just want a Sandbox Server, you are following the wrong guide! You can download a premade server following the instructions here:" + "https://docs.spacestation14.com/en/general-development/setup/server-hosting-tutorial.html \n" + "Closing automatically in 30 seconds.") + time.sleep(30) + exit(1) if __name__ == '__main__': + check_for_zip_download() install_hooks() update_submodules() diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs index 2f87545426..1edbcb6448 100644 --- a/Content.Benchmarks/PvsBenchmark.cs +++ b/Content.Benchmarks/PvsBenchmark.cs @@ -7,6 +7,7 @@ using Content.IntegrationTests; using Content.IntegrationTests.Pair; using Content.Server.Mind; using Content.Server.Warps; +using Content.Shared.Warps; using Robust.Shared; using Robust.Shared.Analyzers; using Robust.Shared.EntitySerialization; diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs index 82f6ebd8b5..c133e0b107 100644 --- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs +++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs @@ -174,7 +174,8 @@ namespace Content.Client.Access.UI new List>()); var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype); - // If the job index is < 0 that means they don't have a job registered in the station records. + // If the job index is < 0 that means they don't have a job registered in the station records + // or the IdCardComponent's JobPrototype field. // For example, a new ID from a box would have no job index. if (jobIndex < 0) { diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 5f0a8e1f2f..31350a6a5d 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -1,6 +1,7 @@ using System.IO; using System.Linq; using Content.Shared.Actions; +using Content.Shared.Charges.Systems; using JetBrains.Annotations; using Robust.Client.Player; using Robust.Shared.ContentPack; @@ -22,6 +23,7 @@ namespace Content.Client.Actions { public delegate void OnActionReplaced(EntityUid actionId); + [Dependency] private readonly SharedChargesSystem _sharedCharges = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IResourceManager _resources = default!; [Dependency] private readonly ISerializationManager _serialization = default!; @@ -51,29 +53,6 @@ namespace Content.Client.Actions SubscribeLocalEvent(OnEntityWorldTargetHandleState); } - public override void FrameUpdate(float frameTime) - { - base.FrameUpdate(frameTime); - - var worldActionQuery = EntityQueryEnumerator(); - while (worldActionQuery.MoveNext(out var uid, out var action)) - { - UpdateAction(uid, action); - } - - var instantActionQuery = EntityQueryEnumerator(); - while (instantActionQuery.MoveNext(out var uid, out var action)) - { - UpdateAction(uid, action); - } - - var entityActionQuery = EntityQueryEnumerator(); - while (entityActionQuery.MoveNext(out var uid, out var action)) - { - UpdateAction(uid, action); - } - } - private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args) { if (args.Current is not InstantActionComponentState state) @@ -127,9 +106,6 @@ namespace Content.Client.Actions component.Toggled = state.Toggled; component.Cooldown = state.Cooldown; component.UseDelay = state.UseDelay; - component.Charges = state.Charges; - component.MaxCharges = state.MaxCharges; - component.RenewCharges = state.RenewCharges; component.Container = EnsureEntity(state.Container, uid); component.EntityIcon = EnsureEntity(state.EntityIcon, uid); component.CheckCanInteract = state.CheckCanInteract; @@ -152,7 +128,8 @@ namespace Content.Client.Actions if (!ResolveActionData(actionId, ref action)) return; - action.IconColor = action.Charges < 1 ? action.DisabledIconColor : action.OriginalIconColor; + // TODO: Decouple this. + action.IconColor = _sharedCharges.GetCurrentCharges(actionId.Value) == 0 ? action.DisabledIconColor : action.OriginalIconColor; base.UpdateAction(actionId, action); if (_playerManager.LocalEntity != action.AttachedEntity) @@ -225,6 +202,7 @@ namespace Content.Client.Actions return; OnActionAdded?.Invoke(actionId); + ActionsUpdated?.Invoke(); } protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action) @@ -233,6 +211,7 @@ namespace Content.Client.Actions return; OnActionRemoved?.Invoke(actionId); + ActionsUpdated?.Invoke(); } public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions() diff --git a/Content.Client/Administration/AdminNameOverlay.cs b/Content.Client/Administration/AdminNameOverlay.cs index c0f31f1e3d..0d424cbff0 100644 --- a/Content.Client/Administration/AdminNameOverlay.cs +++ b/Content.Client/Administration/AdminNameOverlay.cs @@ -6,6 +6,7 @@ using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.Ghost; using Content.Shared.Mind; +using Content.Shared.Roles; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; @@ -22,10 +23,11 @@ internal sealed class AdminNameOverlay : Overlay private readonly IEyeManager _eyeManager; private readonly EntityLookupSystem _entityLookup; private readonly IUserInterfaceManager _userInterfaceManager; + private readonly SharedRoleSystem _roles; private readonly Font _font; private readonly Font _fontBold; - private bool _overlayClassic; - private bool _overlaySymbols; + private AdminOverlayAntagFormat _overlayFormat; + private AdminOverlayAntagSymbolStyle _overlaySymbolStyle; private bool _overlayPlaytime; private bool _overlayStartingJob; private float _ghostFadeDistance; @@ -33,9 +35,10 @@ internal sealed class AdminNameOverlay : Overlay private int _overlayStackMax; private float _overlayMergeDistance; - //TODO make this adjustable via GUI + //TODO make this adjustable via GUI? private readonly ProtoId[] _filter = ["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"]; + private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic"); public AdminNameOverlay( @@ -45,20 +48,22 @@ internal sealed class AdminNameOverlay : Overlay IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager, - IConfigurationManager config) + IConfigurationManager config, + SharedRoleSystem roles) { _system = system; _entityManager = entityManager; _eyeManager = eyeManager; _entityLookup = entityLookup; _userInterfaceManager = userInterfaceManager; + _roles = roles; ZIndex = 200; // Setting these to a specific ttf would break the antag symbols _font = resourceCache.NotoStack(); _fontBold = resourceCache.NotoStack(variation: "Bold"); - config.OnValueChanged(CCVars.AdminOverlayClassic, (show) => { _overlayClassic = show; }, true); - config.OnValueChanged(CCVars.AdminOverlaySymbols, (show) => { _overlaySymbols = show; }, true); + config.OnValueChanged(CCVars.AdminOverlayAntagFormat, (show) => { _overlayFormat = UpdateOverlayFormat(show); }, true); + config.OnValueChanged(CCVars.AdminOverlaySymbolStyle, (show) => { _overlaySymbolStyle = UpdateOverlaySymbolStyle(show); }, true); config.OnValueChanged(CCVars.AdminOverlayPlaytime, (show) => { _overlayPlaytime = show; }, true); config.OnValueChanged(CCVars.AdminOverlayStartingJob, (show) => { _overlayStartingJob = show; }, true); config.OnValueChanged(CCVars.AdminOverlayGhostHideDistance, (f) => { _ghostHideDistance = f; }, true); @@ -67,6 +72,22 @@ internal sealed class AdminNameOverlay : Overlay config.OnValueChanged(CCVars.AdminOverlayMergeDistance, (f) => { _overlayMergeDistance = f; }, true); } + private AdminOverlayAntagFormat UpdateOverlayFormat(string formatString) + { + if (!Enum.TryParse(formatString, out var format)) + format = AdminOverlayAntagFormat.Binary; + + return format; + } + + private AdminOverlayAntagSymbolStyle UpdateOverlaySymbolStyle(string symbolString) + { + if (!Enum.TryParse(symbolString, out var symbolStyle)) + symbolStyle = AdminOverlayAntagSymbolStyle.Off; + + return symbolStyle; + } + public override OverlaySpace Space => OverlaySpace.ScreenSpace; protected override void Draw(in OverlayDrawArgs args) @@ -183,34 +204,56 @@ internal sealed class AdminNameOverlay : Overlay currentOffset += lineoffset; } - // Classic Antag Label - if (_overlayClassic && playerInfo.Antag) + // Determine antag symbol + string? symbol; + switch (_overlaySymbolStyle) { - var symbol = _overlaySymbols ? Loc.GetString("player-tab-antag-prefix") : string.Empty; - var label = _overlaySymbols - ? Loc.GetString("player-tab-character-name-antag-symbol", - ("symbol", symbol), - ("name", _antagLabelClassic)) - : _antagLabelClassic; - color = Color.OrangeRed; - color.A = alpha; - args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color); - currentOffset += lineoffset; + case AdminOverlayAntagSymbolStyle.Specific: + symbol = playerInfo.RoleProto.Symbol; + break; + case AdminOverlayAntagSymbolStyle.Basic: + symbol = Loc.GetString("player-tab-antag-prefix"); + break; + default: + case AdminOverlayAntagSymbolStyle.Off: + symbol = string.Empty; + break; } - // Role Type - else if (!_overlayClassic && _filter.Contains(playerInfo.RoleProto)) + + // Determine antag/role type name + string? text; + switch (_overlayFormat) { - var symbol = _overlaySymbols && playerInfo.Antag ? playerInfo.RoleProto.Symbol : string.Empty; - var role = Loc.GetString(playerInfo.RoleProto.Name).ToUpper(); - var label = _overlaySymbols - ? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", role)) - : role; - color = playerInfo.RoleProto.Color; - color.A = alpha; - args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color); - currentOffset += lineoffset; + 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() + : 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() + : string.Empty; + break; + default: + case AdminOverlayAntagFormat.Binary: + color = Color.OrangeRed; + symbol = playerInfo.Antag ? symbol : string.Empty; + text = playerInfo.Antag ? _antagLabelClassic : string.Empty; + break; } + // Draw antag label + color.A = alpha; + var label = !string.IsNullOrEmpty(symbol) + ? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", text)) + : text; + args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color); + currentOffset += lineoffset; + //Save the coordinates and size of the text block, for stack merge check drawnOverlays.Add((screenCoordinatesCenter, currentOffset)); } diff --git a/Content.Client/Administration/OverlayOptions.cs b/Content.Client/Administration/OverlayOptions.cs new file mode 100644 index 0000000000..39195603bf --- /dev/null +++ b/Content.Client/Administration/OverlayOptions.cs @@ -0,0 +1,15 @@ +namespace Content.Client.Administration; + +public enum AdminOverlayAntagFormat +{ + Binary, + Roletype, + Subtype +} + +public enum AdminOverlayAntagSymbolStyle +{ + Off, + Basic, + Specific +} diff --git a/Content.Client/Administration/Systems/AdminSystem.Overlay.cs b/Content.Client/Administration/Systems/AdminSystem.Overlay.cs index ba56f4694f..a630df4521 100644 --- a/Content.Client/Administration/Systems/AdminSystem.Overlay.cs +++ b/Content.Client/Administration/Systems/AdminSystem.Overlay.cs @@ -1,4 +1,5 @@ using Content.Client.Administration.Managers; +using Content.Shared.Roles; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; @@ -15,6 +16,7 @@ namespace Content.Client.Administration.Systems [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; private AdminNameOverlay _adminNameOverlay = default!; @@ -30,7 +32,8 @@ namespace Content.Client.Administration.Systems _resourceCache, _entityLookup, _userInterfaceManager, - _configurationManager); + _configurationManager, + _roles); _adminManager.AdminStatusUpdated += OnAdminStatusUpdated; } @@ -46,7 +49,8 @@ namespace Content.Client.Administration.Systems public void AdminOverlayOn() { - if (_overlayManager.HasOverlay()) return; + if (_overlayManager.HasOverlay()) + return; _overlayManager.AddOverlay(_adminNameOverlay); OverlayEnabled?.Invoke(); } diff --git a/Content.Client/Administration/Systems/AdminVerbSystem.cs b/Content.Client/Administration/Systems/AdminVerbSystem.cs index dced59bbf2..1e15186706 100644 --- a/Content.Client/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Client/Administration/Systems/AdminVerbSystem.cs @@ -32,7 +32,7 @@ namespace Content.Client.Administration.Systems var verb = new VvVerb() { Text = Loc.GetString("view-variables"), - Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")), Act = () => _clientConsoleHost.ExecuteCommand($"vv {GetNetEntity(args.Target)}"), ClientExclusive = true // opening VV window is client-side. Don't ask server to run this verb. }; diff --git a/Content.Client/Administration/UI/AdminUIHelpers.cs b/Content.Client/Administration/UI/AdminUIHelpers.cs deleted file mode 100644 index 89ab33e931..0000000000 --- a/Content.Client/Administration/UI/AdminUIHelpers.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Threading; -using Content.Client.Stylesheets; -using Robust.Client.UserInterface.Controls; -using Timer = Robust.Shared.Timing.Timer; - -namespace Content.Client.Administration.UI; - -public static class AdminUIHelpers -{ - private static void ResetButton(Button button, ConfirmationData data) - { - data.Cancellation.Cancel(); - button.ModulateSelfOverride = null; - button.Text = data.OriginalText; - } - - public static bool RemoveConfirm(Button button, Dictionary confirmations) - { - if (confirmations.Remove(button, out var data)) - { - ResetButton(button, data); - return true; - } - - return false; - } - - public static void RemoveAllConfirms(Dictionary confirmations) - { - foreach (var (button, confirmation) in confirmations) - { - ResetButton(button, confirmation); - } - - confirmations.Clear(); - } - - public static bool TryConfirm(Button button, Dictionary confirmations) - { - if (RemoveConfirm(button, confirmations)) - return true; - - var data = new ConfirmationData(new CancellationTokenSource(), button.Text); - confirmations[button] = data; - - Timer.Spawn(TimeSpan.FromSeconds(5), () => - { - confirmations.Remove(button); - button.ModulateSelfOverride = null; - button.Text = data.OriginalText; - }, data.Cancellation.Token); - - button.ModulateSelfOverride = StyleNano.ButtonColorCautionDefault; - button.Text = Loc.GetString("admin-player-actions-confirm"); - return false; - } -} - -public readonly record struct ConfirmationData(CancellationTokenSource Cancellation, string? OriginalText); diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml index d53101f68e..2c27fdd2ce 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml +++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml @@ -1,6 +1,7 @@  + xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" + xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"> @@ -18,9 +19,9 @@ - - - - - - - - + + + + + + + - - + + + + + + + + + + + + + + + + + - - - - + + + + + + + +