diff --git a/.github/workflows/build-test-debug.yml b/.github/workflows/build-test-debug.yml index 6b4bd81ef9..21e13b7424 100644 --- a/.github/workflows/build-test-debug.yml +++ b/.github/workflows/build-test-debug.yml @@ -67,7 +67,7 @@ jobs: shell: pwsh run: | $env:DOTNET_gcServer=1 - dotnet test --no-build --configuration DebugOpt Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 + dotnet test --no-build --configuration DebugOpt Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 NUnit.MapWarningTo=Failed ci-success: name: Build & Test Debug needs: diff --git a/.github/workflows/build-test-release.yml b/.github/workflows/build-test-release.yml index 269cec7c35..ccee9d9d31 100644 --- a/.github/workflows/build-test-release.yml +++ b/.github/workflows/build-test-release.yml @@ -67,7 +67,7 @@ jobs: shell: pwsh run: | $env:DOTNET_gcServer=1 - dotnet test --configuration Tools --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 + dotnet test --configuration Tools --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 NUnit.MapWarningTo=Failed ci-success: name: Build & Test Release needs: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 137d5016ae..a07fe6ae76 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,6 +14,9 @@ jobs: environment: "publish" steps: + - name: Install dependencies + run: sudo apt-get install -y python3-paramiko + - uses: actions/checkout@v2 with: submodules: 'recursive' @@ -60,8 +63,13 @@ jobs: port: ${{ secrets.PUBLISH_PORT }} script: /home/${{ secrets.PUBLISH_PUSH_USER }}/push.s1 ${{ github.sha }} - - name: Publish changelog + - name: Publish changelog (Discord) 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) + run: Tools/actions_changelog_rss.py + env: + CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }} diff --git a/Content.Benchmarks/DeviceNetworkingBenchmark.cs b/Content.Benchmarks/DeviceNetworkingBenchmark.cs index 7694c0f37f..8af7f2b262 100644 --- a/Content.Benchmarks/DeviceNetworkingBenchmark.cs +++ b/Content.Benchmarks/DeviceNetworkingBenchmark.cs @@ -26,10 +26,12 @@ public class DeviceNetworkingBenchmark private NetworkPayload _payload = default!; + + [TestPrototypes] private const string Prototypes = @" - type: entity - name: DummyNetworkDevice - id: DummyNetworkDevice + name: DummyNetworkDevicePrivate + id: DummyNetworkDevicePrivate components: - type: DeviceNetwork transmitFrequency: 100 @@ -56,7 +58,7 @@ public class DeviceNetworkingBenchmark public async Task SetupAsync() { ProgramShared.PathOffset = "../../../../"; - _pair = await PoolManager.GetServerClient(new PoolSettings { NoClient = true, ExtraPrototypes = Prototypes }); + _pair = await PoolManager.GetServerClient(); var server = _pair.Pair.Server; await server.WaitPost(() => @@ -73,17 +75,23 @@ await server.WaitPost(() => ["testbool"] = true }; - _sourceEntity = entityManager.SpawnEntity("DummyNetworkDevice", MapCoordinates.Nullspace); + _sourceEntity = entityManager.SpawnEntity("DummyNetworkDevicePrivate", MapCoordinates.Nullspace); _sourceWirelessEntity = entityManager.SpawnEntity("DummyWirelessNetworkDevice", MapCoordinates.Nullspace); for (var i = 0; i < EntityCount; i++) { - _targetEntities.Add(entityManager.SpawnEntity("DummyNetworkDevice", MapCoordinates.Nullspace)); + _targetEntities.Add(entityManager.SpawnEntity("DummyNetworkDevicePrivate", MapCoordinates.Nullspace)); _targetWirelessEntities.Add(entityManager.SpawnEntity("DummyWirelessNetworkDevice", MapCoordinates.Nullspace)); } }); } + [GlobalCleanup] + public async Task Cleanup() + { + await _pair.DisposeAsync(); + } + [Benchmark(Baseline = true, Description = "Entity Events")] public async Task EventSentBaseline() { diff --git a/Content.Benchmarks/Program.cs b/Content.Benchmarks/Program.cs index ae2d7817b9..f5876307a5 100644 --- a/Content.Benchmarks/Program.cs +++ b/Content.Benchmarks/Program.cs @@ -20,6 +20,7 @@ public static void Main(string[] args) public static async Task MainAsync(string[] args) { + PoolManager.Startup(typeof(Program).Assembly); var pair = await PoolManager.GetServerClient(); var gameMaps = pair.Pair.Server.ResolveDependency().EnumeratePrototypes().ToList(); MapLoadBenchmark.MapsSource = gameMaps.Select(x => x.ID); @@ -33,6 +34,8 @@ public static async Task MainAsync(string[] args) var config = Environment.GetEnvironmentVariable("ROBUST_BENCHMARKS_ENABLE_SQL") != null ? DefaultSQLConfig.Instance : null; BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); #endif + + PoolManager.Shutdown(); } } } diff --git a/Content.Client/Access/AccessOverlay.cs b/Content.Client/Access/AccessOverlay.cs index 1ba2dc613f..2be3d07e90 100644 --- a/Content.Client/Access/AccessOverlay.cs +++ b/Content.Client/Access/AccessOverlay.cs @@ -11,14 +11,16 @@ public sealed class AccessOverlay : Overlay { private readonly IEntityManager _entityManager; private readonly EntityLookupSystem _lookup; + private readonly SharedTransformSystem _xform; private readonly Font _font; public override OverlaySpace Space => OverlaySpace.ScreenSpace; - public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup) + public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup, SharedTransformSystem xform) { _entityManager = entManager; _lookup = lookup; + _xform = xform; _font = cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12); } @@ -71,7 +73,7 @@ protected override void Draw(in OverlayDrawArgs args) textStr = ""; } - var screenPos = args.ViewportControl.WorldToScreen(xform.WorldPosition); + var screenPos = args.ViewportControl.WorldToScreen(_xform.GetWorldPosition(xform)); args.ScreenHandle.DrawString(_font, screenPos, textStr, Color.Gold); } diff --git a/Content.Client/Access/AccessSystem.cs b/Content.Client/Access/AccessSystem.cs index a5680ab937..a510d6cae7 100644 --- a/Content.Client/Access/AccessSystem.cs +++ b/Content.Client/Access/AccessSystem.cs @@ -2,4 +2,6 @@ namespace Content.Client.Access; -public sealed class AccessSystem : SharedAccessSystem {} +public sealed class AccessSystem : SharedAccessSystem +{ +} diff --git a/Content.Client/Access/Commands/ShowAccessReadersCommand.cs b/Content.Client/Access/Commands/ShowAccessReadersCommand.cs index fa26df1944..7c804dd969 100644 --- a/Content.Client/Access/Commands/ShowAccessReadersCommand.cs +++ b/Content.Client/Access/Commands/ShowAccessReadersCommand.cs @@ -13,7 +13,8 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) { var collection = IoCManager.Instance; - if (collection == null) return; + if (collection == null) + return; var overlay = collection.Resolve(); @@ -25,9 +26,10 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) var entManager = collection.Resolve(); var cache = collection.Resolve(); - var system = entManager.EntitySysManager.GetEntitySystem(); + var lookup = entManager.System(); + var xform = entManager.System(); - overlay.AddOverlay(new AccessOverlay(entManager, cache, system)); + overlay.AddOverlay(new AccessOverlay(entManager, cache, lookup, xform)); shell.WriteLine($"Set access reader debug overlay to true"); } } diff --git a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs index a7c499c12e..6eae796856 100644 --- a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs +++ b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs @@ -56,9 +56,10 @@ protected override void UpdateState(BoundUserInterfaceState state) protected override void Dispose(bool disposing) { base.Dispose(disposing); - if (!disposing) return; + if (!disposing) + return; + _window?.Dispose(); } } - } diff --git a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs index f1350a21b4..292759dc87 100644 --- a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs +++ b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs @@ -51,7 +51,9 @@ protected override void Open() protected override void Dispose(bool disposing) { base.Dispose(disposing); - if (!disposing) return; + if (!disposing) + return; + _window?.Dispose(); } diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs index 9bc410db0f..f8450fe578 100644 --- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs +++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs @@ -15,6 +15,8 @@ namespace Content.Client.Access.UI public sealed partial class IdCardConsoleWindow : DefaultWindow { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ILogManager _logManager = default!; + private readonly ISawmill _logMill = default!; private readonly IdCardConsoleBoundUserInterface _owner; @@ -30,6 +32,7 @@ public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeMana { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); + _logMill = _logManager.GetSawmill(SharedIdCardConsoleSystem.Sawmill); _owner = owner; @@ -67,7 +70,7 @@ public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeMana { if (!prototypeManager.TryIndex(access, out var accessLevel)) { - Logger.ErrorS(SharedIdCardConsoleSystem.Sawmill, $"Unable to find accesslevel for {access}"); + _logMill.Error($"Unable to find accesslevel for {access}"); continue; } @@ -116,7 +119,7 @@ private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args) // this is a sussy way to do this foreach (var access in job.Access) { - if (_accessButtons.TryGetValue(access, out var button)) + if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled) { button.Pressed = true; } @@ -131,7 +134,7 @@ private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args) foreach (var access in groupPrototype.Tags) { - if (_accessButtons.TryGetValue(access, out var button)) + if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled) { button.Pressed = true; } @@ -187,6 +190,7 @@ public void UpdateState(IdCardConsoleBoundUserInterfaceState state) if (interfaceEnabled) { button.Pressed = state.TargetIdAccessList?.Contains(accessName) ?? false; + button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true; } } diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml index 64eb7d206c..b8f91e050e 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml @@ -12,7 +12,7 @@ - + diff --git a/Content.Client/Animations/AnimationsTestComponent.cs b/Content.Client/Animations/AnimationsTestComponent.cs deleted file mode 100644 index f951295ed5..0000000000 --- a/Content.Client/Animations/AnimationsTestComponent.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using Robust.Client.Animations; -using Robust.Client.GameObjects; -using Robust.Shared.Animations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; - -namespace Content.Client.Animations -{ - [RegisterComponent] - public sealed class AnimationsTestComponent : Component - { - protected override void Initialize() - { - base.Initialize(); - - var animations = IoCManager.Resolve().GetComponent(Owner); - animations.Play(new Animation - { - Length = TimeSpan.FromSeconds(20), - AnimationTracks = - { - new AnimationTrackComponentProperty - { - ComponentType = typeof(TransformComponent), - Property = nameof(TransformComponent.LocalRotation), - InterpolationMode = AnimationInterpolationMode.Linear, - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(Angle.Zero, 0), - new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(1440), 20) - } - }, - new AnimationTrackComponentProperty - { - ComponentType = typeof(SpriteComponent), - Property = "layer/0/texture", - KeyFrames = - { - new AnimationTrackProperty.KeyFrame("Objects/toolbox_r.png", 0), - new AnimationTrackProperty.KeyFrame("Objects/Toolbox_b.png", 5), - new AnimationTrackProperty.KeyFrame("Objects/Toolbox_y.png", 5), - new AnimationTrackProperty.KeyFrame("Objects/toolbox_r.png", 5), - } - } - } - }, "yes"); - } - } -} diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs index 0bd310c57c..aea0ce41e8 100644 --- a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs +++ b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs @@ -33,6 +33,7 @@ protected override void Open() _window.AtmosDeviceDataChanged += OnDeviceDataChanged; _window.AtmosAlarmThresholdChanged += OnThresholdChanged; _window.AirAlarmModeChanged += OnAirAlarmModeChanged; + _window.AutoModeChanged += OnAutoModeChanged; _window.ResyncAllRequested += ResyncAllDevices; _window.AirAlarmTabChange += OnTabChanged; } @@ -52,6 +53,11 @@ private void OnAirAlarmModeChanged(AirAlarmMode mode) SendMessage(new AirAlarmUpdateAlarmModeMessage(mode)); } + private void OnAutoModeChanged(bool enabled) + { + SendMessage(new AirAlarmUpdateAutoModeMessage(enabled)); + } + private void OnThresholdChanged(string address, AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null) { SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas)); diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml index 104b40aa54..aaa9317461 100644 --- a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml +++ b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml @@ -75,6 +75,7 @@ diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs index bb3f0dcbfa..105394b648 100644 --- a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs +++ b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs @@ -20,6 +20,7 @@ public sealed partial class AirAlarmWindow : FancyWindow public event Action? AtmosDeviceDataChanged; public event Action? AtmosAlarmThresholdChanged; public event Action? AirAlarmModeChanged; + public event Action? AutoModeChanged; public event Action? ResyncDeviceRequested; public event Action? ResyncAllRequested; public event Action? AirAlarmTabChange; @@ -44,6 +45,8 @@ public sealed partial class AirAlarmWindow : FancyWindow private OptionButton _modes => CModeButton; + private CheckBox _autoMode => AutoModeCheckBox; + public AirAlarmWindow(BoundUserInterface owner) { RobustXamlLoader.Load(this); @@ -68,6 +71,11 @@ public AirAlarmWindow(BoundUserInterface owner) AirAlarmModeChanged!.Invoke((AirAlarmMode) args.Id); }; + _autoMode.OnToggled += args => + { + AutoModeChanged!.Invoke(_autoMode.Pressed); + }; + _tabContainer.SetTabTitle(0, Loc.GetString("air-alarm-ui-window-tab-vents")); _tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers")); _tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors")); @@ -101,6 +109,7 @@ public void UpdateState(AirAlarmUIState state) ("color", ColorForAlarm(state.AlarmType)), ("state", $"{state.AlarmType}"))); UpdateModeSelector(state.Mode); + UpdateAutoMode(state.AutoMode); foreach (var (addr, dev) in state.DeviceData) { UpdateDeviceData(addr, dev); @@ -114,6 +123,11 @@ public void UpdateModeSelector(AirAlarmMode mode) _modes.SelectId((int) mode); } + public void UpdateAutoMode(bool enabled) + { + _autoMode.Pressed = enabled; + } + public void UpdateDeviceData(string addr, IAtmosDeviceData device) { switch (device) diff --git a/Content.Client/Bql/BqlResultsEui.cs b/Content.Client/Bql/ToolshedVisualizeEui.cs similarity index 74% rename from Content.Client/Bql/BqlResultsEui.cs rename to Content.Client/Bql/ToolshedVisualizeEui.cs index 3e9bda57ff..ccefc1228d 100644 --- a/Content.Client/Bql/BqlResultsEui.cs +++ b/Content.Client/Bql/ToolshedVisualizeEui.cs @@ -7,13 +7,13 @@ namespace Content.Client.Bql; [UsedImplicitly] -public sealed class BqlResultsEui : BaseEui +public sealed class ToolshedVisualizeEui : BaseEui { - private readonly BqlResultsWindow _window; + private readonly ToolshedVisualizeWindow _window; - public BqlResultsEui() + public ToolshedVisualizeEui() { - _window = new BqlResultsWindow( + _window = new ToolshedVisualizeWindow( IoCManager.Resolve(), IoCManager.Resolve() ); @@ -23,7 +23,7 @@ public BqlResultsEui() public override void HandleState(EuiStateBase state) { - if (state is not BqlResultsEuiState castState) + if (state is not ToolshedVisualizeEuiState castState) return; _window.Update(castState.Entities); diff --git a/Content.Client/Bql/BqlResultsWindow.xaml b/Content.Client/Bql/ToolshedVisualizeWindow.xaml similarity index 100% rename from Content.Client/Bql/BqlResultsWindow.xaml rename to Content.Client/Bql/ToolshedVisualizeWindow.xaml diff --git a/Content.Client/Bql/BqlResultsWindow.xaml.cs b/Content.Client/Bql/ToolshedVisualizeWindow.xaml.cs similarity index 91% rename from Content.Client/Bql/BqlResultsWindow.xaml.cs rename to Content.Client/Bql/ToolshedVisualizeWindow.xaml.cs index 4a9fde855a..0265e3343e 100644 --- a/Content.Client/Bql/BqlResultsWindow.xaml.cs +++ b/Content.Client/Bql/ToolshedVisualizeWindow.xaml.cs @@ -8,12 +8,12 @@ namespace Content.Client.Bql; [GenerateTypedNameReferences] -internal sealed partial class BqlResultsWindow : DefaultWindow +internal sealed partial class ToolshedVisualizeWindow : DefaultWindow { private readonly IClientConsoleHost _console; private readonly ILocalizationManager _loc; - public BqlResultsWindow(IClientConsoleHost console, ILocalizationManager loc) + public ToolshedVisualizeWindow(IClientConsoleHost console, ILocalizationManager loc) { _console = console; _loc = loc; diff --git a/Content.Client/CartridgeLoader/Cartridges/NewsReadUi.cs b/Content.Client/CartridgeLoader/Cartridges/NewsReadUi.cs new file mode 100644 index 0000000000..ce240e53a0 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/NewsReadUi.cs @@ -0,0 +1,50 @@ +using Content.Client.UserInterface.Fragments; +using Content.Shared.CartridgeLoader.Cartridges; +using Content.Shared.CartridgeLoader; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; + +namespace Content.Client.CartridgeLoader.Cartridges; + +public sealed class NewsReadUi : UIFragment +{ + private NewsReadUiFragment? _fragment; + + public override Control GetUIFragmentRoot() + { + return _fragment!; + } + + public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner) + { + _fragment = new NewsReadUiFragment(); + + _fragment.OnNextButtonPressed += () => + { + SendNewsReadMessage(NewsReadUiAction.Next, userInterface); + }; + _fragment.OnPrevButtonPressed += () => + { + SendNewsReadMessage(NewsReadUiAction.Prev, userInterface); + }; + _fragment.OnNotificationSwithPressed += () => + { + SendNewsReadMessage(NewsReadUiAction.NotificationSwith, userInterface); + }; + } + + public override void UpdateState(BoundUserInterfaceState state) + { + if (state is NewsReadBoundUserInterfaceState cast) + _fragment?.UpdateState(cast.Article, cast.TargetNum, cast.TotalNum, cast.NotificationOn); + else if (state is NewsReadEmptyBoundUserInterfaceState empty) + _fragment?.UpdateEmptyState(empty.NotificationOn); + } + + private void SendNewsReadMessage(NewsReadUiAction action, BoundUserInterface userInterface) + { + var newsMessage = new NewsReadUiMessageEvent(action); + var message = new CartridgeUiMessage(newsMessage); + userInterface.SendMessage(message); + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/NewsReadUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/NewsReadUiFragment.xaml new file mode 100644 index 0000000000..7431713ea8 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/NewsReadUiFragment.xaml @@ -0,0 +1,54 @@ + + + + + + + diff --git a/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml.cs b/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml.cs new file mode 100644 index 0000000000..6d05505bcb --- /dev/null +++ b/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml.cs @@ -0,0 +1,45 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Content.Shared.MassMedia.Systems; + +namespace Content.Client.MassMedia.Ui; + +[GenerateTypedNameReferences] +public sealed partial class NewsWriteMenu : DefaultWindow +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public event Action? ShareButtonPressed; + public event Action? DeleteButtonPressed; + + public NewsWriteMenu(string name) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + if (Window != null) + Window.Title = name; + + Share.OnPressed += _ => ShareButtonPressed?.Invoke(); + } + + public void UpdateUI(NewsArticle[] articles, bool shareAvalible) + { + ArticleCardsContainer.Children.Clear(); + + for (int i = 0; i < articles.Length; i++) + { + var article = articles[i]; + var mini = new MiniArticleCardControl(article.Name, (article.Author != null ? article.Author : Loc.GetString("news-read-ui-no-author"))); + mini.ArtcileNum = i; + mini.OnDeletePressed += () => DeleteButtonPressed?.Invoke(mini.ArtcileNum); + + ArticleCardsContainer.AddChild(mini); + } + + Share.Disabled = !shareAvalible; + } +} diff --git a/Content.Client/PDA/PdaMenu.xaml b/Content.Client/PDA/PdaMenu.xaml index a06032d6d2..dbdcb4d90b 100644 --- a/Content.Client/PDA/PdaMenu.xaml +++ b/Content.Client/PDA/PdaMenu.xaml @@ -1,4 +1,4 @@ -(component.RelayEntity, out var inputMover)) + if (MoverQuery.TryGetComponent(component.RelayEntity, out var inputMover)) SetMoveInput(inputMover, MoveButtons.None); } @@ -66,7 +66,7 @@ private void OnRelayPlayerDetached(EntityUid uid, RelayInputMoverComponent compo { Physics.UpdateIsPredicted(uid); Physics.UpdateIsPredicted(component.RelayEntity); - if (TryComp(component.RelayEntity, out var inputMover)) + if (MoverQuery.TryGetComponent(component.RelayEntity, out var inputMover)) SetMoveInput(inputMover, MoveButtons.None); } @@ -87,7 +87,7 @@ public override void UpdateBeforeSolve(bool prediction, float frameTime) if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} player) return; - if (TryComp(player, out var relayMover)) + if (RelayQuery.TryGetComponent(player, out var relayMover)) HandleClientsideMovement(relayMover.RelayEntity, frameTime); HandleClientsideMovement(player, frameTime); @@ -95,15 +95,8 @@ public override void UpdateBeforeSolve(bool prediction, float frameTime) private void HandleClientsideMovement(EntityUid player, float frameTime) { - var xformQuery = GetEntityQuery(); - var moverQuery = GetEntityQuery(); - var relayTargetQuery = GetEntityQuery(); - var mobMoverQuery = GetEntityQuery(); - var pullableQuery = GetEntityQuery(); - var modifierQuery = GetEntityQuery(); - - if (!moverQuery.TryGetComponent(player, out var mover) || - !xformQuery.TryGetComponent(player, out var xform)) + if (!MoverQuery.TryGetComponent(player, out var mover) || + !XformQuery.TryGetComponent(player, out var xform)) { return; } @@ -112,17 +105,17 @@ private void HandleClientsideMovement(EntityUid player, float frameTime) PhysicsComponent? body; var xformMover = xform; - if (mover.ToParent && HasComp(xform.ParentUid)) + if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid)) { - if (!TryComp(xform.ParentUid, out body) || - !TryComp(xform.ParentUid, out xformMover)) + if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) || + !XformQuery.TryGetComponent(xform.ParentUid, out xformMover)) { return; } physicsUid = xform.ParentUid; } - else if (!TryComp(player, out body)) + else if (!PhysicsQuery.TryGetComponent(player, out body)) { return; } @@ -134,13 +127,7 @@ private void HandleClientsideMovement(EntityUid player, float frameTime) physicsUid, body, xformMover, - frameTime, - xformQuery, - moverQuery, - mobMoverQuery, - relayTargetQuery, - pullableQuery, - modifierQuery); + frameTime); } protected override bool CanSound() diff --git a/Content.Client/Popups/PopupOverlay.cs b/Content.Client/Popups/PopupOverlay.cs index 17af5ca38f..5adc2e1ff0 100644 --- a/Content.Client/Popups/PopupOverlay.cs +++ b/Content.Client/Popups/PopupOverlay.cs @@ -115,10 +115,12 @@ private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args, private void DrawPopup(PopupSystem.PopupLabel popup, DrawingHandleScreen handle, Vector2 position, float scale) { - const float alphaMinimum = 0.5f; + var lifetime = PopupSystem.GetPopupLifetime(popup); - var alpha = MathF.Min(1f, 1f - (popup.TotalTime - alphaMinimum) / (PopupSystem.PopupLifetime - alphaMinimum)); - var updatedPosition = position - new Vector2(0f, 20f * (popup.TotalTime * popup.TotalTime + popup.TotalTime)); + // Keep alpha at 1 until TotalTime passes half its lifetime, then gradually decrease to 0. + var alpha = MathF.Min(1f, 1f - MathF.Max(0f, popup.TotalTime - lifetime / 2) * 2 / lifetime); + + var updatedPosition = position - new Vector2(0f, MathF.Min(8f, 12f * (popup.TotalTime * popup.TotalTime + popup.TotalTime))); var font = _smallFont; var color = Color.White.WithAlpha(alpha); diff --git a/Content.Client/Popups/PopupSystem.cs b/Content.Client/Popups/PopupSystem.cs index 645c2f105c..b678a0aaa4 100644 --- a/Content.Client/Popups/PopupSystem.cs +++ b/Content.Client/Popups/PopupSystem.cs @@ -37,7 +37,9 @@ public sealed class PopupSystem : SharedPopupSystem private readonly List _aliveWorldLabels = new(); private readonly List _aliveCursorLabels = new(); - public const float PopupLifetime = 3f; + public const float MinimumPopupLifetime = 0.7f; + public const float MaximumPopupLifetime = 5f; + public const float PopupLifetimePerCharacter = 0.04f; public override void Initialize() { @@ -201,6 +203,13 @@ private void OnRoundRestart(RoundRestartCleanupEvent ev) #endregion + public static float GetPopupLifetime(PopupLabel label) + { + return Math.Clamp(PopupLifetimePerCharacter * label.Text.Length, + MinimumPopupLifetime, + MaximumPopupLifetime); + } + public override void FrameUpdate(float frameTime) { if (_aliveWorldLabels.Count == 0 && _aliveCursorLabels.Count == 0) @@ -211,7 +220,7 @@ public override void FrameUpdate(float frameTime) var label = _aliveWorldLabels[i]; label.TotalTime += frameTime; - if (label.TotalTime > PopupLifetime || Deleted(label.InitialPos.EntityId)) + if (label.TotalTime > GetPopupLifetime(label) || Deleted(label.InitialPos.EntityId)) { _aliveWorldLabels.RemoveSwap(i); i--; @@ -223,7 +232,7 @@ public override void FrameUpdate(float frameTime) var label = _aliveCursorLabels[i]; label.TotalTime += frameTime; - if (label.TotalTime > PopupLifetime) + if (label.TotalTime > GetPopupLifetime(label)) { _aliveCursorLabels.RemoveSwap(i); i--; diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs index f1feb9320f..334aeb3d98 100644 --- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs @@ -1482,7 +1482,7 @@ public JobPrioritySelector(JobPrototype job) { Margin = new Thickness(5f,0,5f,0), Text = job.LocalizedName, - MinSize = new Vector2(180, 0), + MinSize = new Vector2(200, 0), MouseFilter = MouseFilterMode.Stop }; diff --git a/Content.Client/Radiation/Systems/GeigerSystem.cs b/Content.Client/Radiation/Systems/GeigerSystem.cs index 600c5860fb..f3adccf0fc 100644 --- a/Content.Client/Radiation/Systems/GeigerSystem.cs +++ b/Content.Client/Radiation/Systems/GeigerSystem.cs @@ -17,7 +17,6 @@ public sealed class GeigerSystem : SharedGeigerSystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnAttachedEntityChanged); SubscribeLocalEvent(OnHandleState); SubscribeLocalEvent(OnGetStatusMessage); } @@ -27,8 +26,6 @@ private void OnHandleState(EntityUid uid, GeigerComponent component, ref Compone if (args.Current is not GeigerComponentState state) return; - UpdateGeigerSound(uid, state.IsEnabled, state.User, state.DangerLevel, false, component); - component.CurrentRadiation = state.CurrentRadiation; component.DangerLevel = state.DangerLevel; component.IsEnabled = state.IsEnabled; @@ -43,53 +40,4 @@ private void OnGetStatusMessage(EntityUid uid, GeigerComponent component, ItemSt args.Controls.Add(new GeigerItemControl(component)); } - - private void OnAttachedEntityChanged(PlayerAttachSysMessage ev) - { - // need to go for each component known to client - // and update their geiger sound - foreach (var geiger in EntityQuery()) - { - ForceUpdateGeigerSound(geiger.Owner, geiger); - } - } - - private void ForceUpdateGeigerSound(EntityUid uid, GeigerComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - UpdateGeigerSound(uid, component.IsEnabled, component.User, component.DangerLevel, true, component); - } - - private void UpdateGeigerSound(EntityUid uid, bool isEnabled, EntityUid? user, - GeigerDangerLevel dangerLevel, bool force = false, GeigerComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - // check if we even need to update sound - if (!force && isEnabled == component.IsEnabled && - user == component.User && dangerLevel == component.DangerLevel) - { - return; - } - - component.Stream?.Stop(); - - if (!isEnabled || user == null) - return; - if (!component.Sounds.TryGetValue(dangerLevel, out var sounds)) - return; - - // check that that local player controls entity that is holding geiger counter - if (_playerManager.LocalPlayer == null) - return; - var attachedEnt = _playerManager.LocalPlayer.Session.AttachedEntity; - if (attachedEnt != user) - return; - - var sound = _audio.GetSound(sounds); - var param = sounds.Params.WithLoop(true).WithVolume(-4f); - component.Stream = _audio.Play(sound, Filter.Local(), uid, false, param); - } } diff --git a/Content.Client/Replay/ContentReplayPlaybackManager.cs b/Content.Client/Replay/ContentReplayPlaybackManager.cs index 16cdfb79fe..55491429ea 100644 --- a/Content.Client/Replay/ContentReplayPlaybackManager.cs +++ b/Content.Client/Replay/ContentReplayPlaybackManager.cs @@ -120,6 +120,9 @@ private bool OnHandleReplayMessage(object message, bool skipEffects) if (!_entMan.EntityExists(_player.LocalPlayer?.ControlledEntity)) _entMan.System().SetSpectatorPosition(default); return true; + case ChatMessage chat: + _uiMan.GetUIController().ProcessChatMessage(chat, speechBubble: !skipEffects); + return true; } if (!skipEffects) @@ -130,10 +133,6 @@ private bool OnHandleReplayMessage(object message, bool skipEffects) switch (message) { - case ChatMessage chat: - // Pass the chat message to the UI controller, skip the speech-bubble / pop-up. - _uiMan.GetUIController().ProcessChatMessage(chat, speechBubble: false); - return true; case RoundEndMessageEvent: case PopupEvent: case AudioMessage: diff --git a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Movement.cs b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Movement.cs index f15385adca..2b72d2563b 100644 --- a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Movement.cs +++ b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Movement.cs @@ -50,7 +50,8 @@ public override void FrameUpdate(float frameTime) if (Direction == DirectionFlag.None) { if (TryComp(player, out InputMoverComponent? cmp)) - _mover.LerpRotation(cmp, frameTime); + _mover.LerpRotation(player, cmp, frameTime); + return; } @@ -64,7 +65,7 @@ public override void FrameUpdate(float frameTime) if (!TryComp(player, out InputMoverComponent? mover)) return; - _mover.LerpRotation(mover, frameTime); + _mover.LerpRotation(player, mover, frameTime); var effectiveDir = Direction; if ((Direction & DirectionFlag.North) != 0) @@ -75,7 +76,7 @@ public override void FrameUpdate(float frameTime) var query = GetEntityQuery(); var xform = query.GetComponent(player); - var pos = _transform.GetWorldPosition(xform, query); + var pos = _transform.GetWorldPosition(xform); if (!xform.ParentUid.IsValid()) { @@ -93,12 +94,12 @@ public override void FrameUpdate(float frameTime) if (xform.ParentUid.IsValid()) _transform.SetGridId(player, xform, Transform(xform.ParentUid).GridUid); - var parentRotation = _mover.GetParentGridAngle(mover, query); + var parentRotation = _mover.GetParentGridAngle(mover); var localVec = effectiveDir.AsDir().ToAngle().ToWorldVec(); var worldVec = parentRotation.RotateVec(localVec); var speed = CompOrNull(player)?.BaseSprintSpeed ?? DefaultSpeed; var delta = worldVec * frameTime * speed; - _transform.SetWorldPositionRotation(xform, pos + delta, delta.ToWorldAngle(), query); + _transform.SetWorldPositionRotation(xform, pos + delta, delta.ToWorldAngle()); } private sealed class MoverHandler : InputCmdHandler diff --git a/Content.Client/Salvage/SalvageMagnetComponent.cs b/Content.Client/Salvage/SalvageMagnetComponent.cs index 44b8b9d958..a681c00d7b 100644 --- a/Content.Client/Salvage/SalvageMagnetComponent.cs +++ b/Content.Client/Salvage/SalvageMagnetComponent.cs @@ -1,5 +1,7 @@ using Content.Shared.Salvage; using Robust.Shared.GameStates; +namespace Content.Client.Salvage; + [NetworkedComponent, RegisterComponent] public sealed class SalvageMagnetComponent : SharedSalvageMagnetComponent {} diff --git a/Content.Client/Spawners/ClientEntitySpawnerComponent.cs b/Content.Client/Spawners/ClientEntitySpawnerComponent.cs deleted file mode 100644 index c9a32c1a2c..0000000000 --- a/Content.Client/Spawners/ClientEntitySpawnerComponent.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Client.Spawners -{ - /// - /// Spawns a set of entities on the client only, and removes them when this component is removed. - /// - [RegisterComponent] - [ComponentProtoName("ClientEntitySpawner")] - public sealed class ClientEntitySpawnerComponent : Component - { - [Dependency] private readonly IEntityManager _entMan = default!; - - [DataField("prototypes")] private List _prototypes = new() { "HVDummyWire" }; - - private readonly List _entity = new(); - - protected override void Initialize() - { - base.Initialize(); - SpawnEntities(); - } - - protected override void OnRemove() - { - RemoveEntities(); - base.OnRemove(); - } - - private void SpawnEntities() - { - foreach (var proto in _prototypes) - { - var entity = _entMan.SpawnEntity(proto, _entMan.GetComponent(Owner).Coordinates); - _entity.Add(entity); - } - } - - private void RemoveEntities() - { - foreach (var entity in _entity) - { - _entMan.DeleteEntity(entity); - } - } - } -} diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs index b4a1d83fc7..73858e0c9e 100644 --- a/Content.Client/Stylesheets/StyleNano.cs +++ b/Content.Client/Stylesheets/StyleNano.cs @@ -164,6 +164,7 @@ public StyleNano(IResourceCache resCache) : base(resCache) var notoSansBold16 = resCache.NotoStack(variation: "Bold", size: 16); var notoSansBold18 = resCache.NotoStack(variation: "Bold", size: 18); var notoSansBold20 = resCache.NotoStack(variation: "Bold", size: 20); + var notoSansMono = resCache.GetFont("/EngineFonts/NotoSans/NotoSansMono-Regular.ttf", size: 12); var windowHeaderTex = resCache.GetTexture("/Textures/Interface/Nano/window_header.png"); var windowHeader = new StyleBoxTexture { @@ -514,6 +515,8 @@ public StyleNano(IResourceCache resCache) : base(resCache) Stylesheet = new Stylesheet(BaseRules.Concat(new[] { + Element().Class("monospace") + .Prop("font", notoSansMono), // Window title. new StyleRule( new SelectorElement(typeof(Label), new[] {DefaultWindow.StyleClassWindowTitle}, null, null), diff --git a/Content.Client/UserInterface/ControlExtensions/ControlExtension.cs b/Content.Client/UserInterface/ControlExtensions/ControlExtension.cs new file mode 100644 index 0000000000..c0e4a038a1 --- /dev/null +++ b/Content.Client/UserInterface/ControlExtensions/ControlExtension.cs @@ -0,0 +1,96 @@ +using Content.Client.Guidebook.Controls; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.UserInterface.ControlExtensions; + +public static class ControlExtension +{ + public static List GetControlOfType(this Control parent) where T : Control + { + return parent.GetControlOfType(typeof(T).Name, false); + } + public static List GetControlOfType(this Control parent, string childType) where T : Control + { + return parent.GetControlOfType(childType, false); + } + + public static List GetControlOfType(this Control parent, bool fullTreeSearch) where T : Control + { + return parent.GetControlOfType(typeof(T).Name, fullTreeSearch); + } + + public static List GetControlOfType(this Control parent, string childType, bool fullTreeSearch) where T : Control + { + List controlList = new List(); + + foreach (var child in parent.Children) + { + var isType = child.GetType().Name == childType; + var hasChildren = child.ChildCount > 0; + + var searchDeeper = hasChildren && !isType; + + if (isType) + { + controlList.Add((T) child); + } + + if (fullTreeSearch || searchDeeper) + { + controlList.AddRange(child.GetControlOfType(childType, fullTreeSearch)); + } + } + + return controlList; + } + + public static List GetSearchableControls(this Control parent, bool fullTreeSearch = false) + { + List controlList = new List(); + + foreach (var child in parent.Children) + { + var hasChildren = child.ChildCount > 0; + var searchDeeper = hasChildren && child is not ISearchableControl; + + if (child is ISearchableControl searchableChild) + { + controlList.Add(searchableChild); + } + + if (fullTreeSearch || searchDeeper) + { + controlList.AddRange(child.GetSearchableControls(fullTreeSearch)); + } + } + + return controlList; + } + + public static bool ChildrenContainText(this Control parent, string search) + { + var labels = parent.GetControlOfType