diff --git a/.editorconfig b/.editorconfig index 872a068c7c6..3e44d1a2811 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,7 @@ tab_width = 4 #end_of_line = crlf insert_final_newline = true trim_trailing_whitespace = true +max_line_length = 120 #### .NET Coding Conventions #### @@ -336,7 +337,11 @@ dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter # ReSharper properties resharper_braces_for_ifelse = required_for_multiline +resharper_csharp_wrap_arguments_style = chop_if_long +resharper_csharp_wrap_parameters_style = chop_if_long resharper_keep_existing_attribute_arrangement = true +resharper_wrap_chained_binary_patterns = chop_if_long +resharper_wrap_chained_method_calls = chop_if_long [*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}] indent_size = 2 diff --git a/.github/workflows/build-map-renderer.yml b/.github/workflows/build-map-renderer.yml index e16d951cfae..01575f64b9b 100644 --- a/.github/workflows/build-map-renderer.yml +++ b/.github/workflows/build-map-renderer.yml @@ -36,7 +36,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 8.0.100 + dotnet-version: 8.0.x - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/build-test-debug.yml b/.github/workflows/build-test-debug.yml index 70dc7d3b11c..519f5af6f49 100644 --- a/.github/workflows/build-test-debug.yml +++ b/.github/workflows/build-test-debug.yml @@ -36,7 +36,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 8.0.100 + dotnet-version: 8.0.x - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 177e6a0fe6d..74a975be5c6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,7 +22,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 8.0.100 + dotnet-version: 8.0.x - name: Get Engine Tag run: | diff --git a/.github/workflows/test-packaging.yml b/.github/workflows/test-packaging.yml index 745f8c092c4..ccece69adb6 100644 --- a/.github/workflows/test-packaging.yml +++ b/.github/workflows/test-packaging.yml @@ -51,7 +51,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 8.0.100 + dotnet-version: 8.0.x - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/yaml-linter.yml b/.github/workflows/yaml-linter.yml index d24991ec963..796795b234d 100644 --- a/.github/workflows/yaml-linter.yml +++ b/.github/workflows/yaml-linter.yml @@ -26,7 +26,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 8.0.100 + dotnet-version: 8.0.x - name: Install dependencies run: dotnet restore - name: Build diff --git a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs index 5b7011c195a..a321b4121e5 100644 --- a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs +++ b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs @@ -1,6 +1,5 @@ using Content.Shared.Access; using Content.Shared.Access.Components; -using Content.Shared.Access; using Content.Shared.Access.Systems; using Content.Shared.Containers.ItemSlots; using Content.Shared.CrewManifest; diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index b992e772563..b9d554607cf 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -248,7 +248,10 @@ public void TriggerAction(EntityUid actionId, BaseActionComponent action) if (action.ClientExclusive) { if (instantAction.Event != null) + { instantAction.Event.Performer = user; + instantAction.Event.Action = actionId; + } PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime); } diff --git a/Content.Client/Administration/Systems/AdminFrozenSystem.cs b/Content.Client/Administration/Systems/AdminFrozenSystem.cs new file mode 100644 index 00000000000..885585f985c --- /dev/null +++ b/Content.Client/Administration/Systems/AdminFrozenSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Administration; + +namespace Content.Client.Administration.Systems; + +public sealed class AdminFrozenSystem : SharedAdminFrozenSystem +{ +} diff --git a/Content.Client/Administration/UI/SpawnExplosion/SpawnExplosionWindow.xaml.cs b/Content.Client/Administration/UI/SpawnExplosion/SpawnExplosionWindow.xaml.cs index 5f187cad794..b0d8a946ec5 100644 --- a/Content.Client/Administration/UI/SpawnExplosion/SpawnExplosionWindow.xaml.cs +++ b/Content.Client/Administration/UI/SpawnExplosion/SpawnExplosionWindow.xaml.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using Robust.Client.AutoGenerated; using Robust.Client.Console; +using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; @@ -22,7 +23,7 @@ public sealed partial class SpawnExplosionWindow : DefaultWindow [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IEntityManager _entMan = default!; - + private readonly SharedTransformSystem _transform = default!; private readonly SpawnExplosionEui _eui; private List _mapData = new(); @@ -37,6 +38,7 @@ public SpawnExplosionWindow(SpawnExplosionEui eui) { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); + _transform = _entMan.System(); _eui = eui; ExplosionOption.OnItemSelected += ExplosionSelected; @@ -104,7 +106,7 @@ private void SetLocation() _pausePreview = true; MapOptions.Select(_mapData.IndexOf(transform.MapID)); - (MapX.Value, MapY.Value) = transform.MapPosition.Position; + (MapX.Value, MapY.Value) = _transform.GetMapCoordinates(_playerManager.LocalEntity!.Value, xform: transform).Position; _pausePreview = false; UpdatePreview(); diff --git a/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs b/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs index 072730d65d4..60fe339069a 100644 --- a/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs +++ b/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs @@ -9,7 +9,6 @@ namespace Content.Client.Audio.Jukebox; public sealed class JukeboxBoundUserInterface : BoundUserInterface { - [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!; [ViewVariables] diff --git a/Content.Client/Changelog/ChangelogWindow.xaml.cs b/Content.Client/Changelog/ChangelogWindow.xaml.cs index e5f492900c2..9b7fd754369 100644 --- a/Content.Client/Changelog/ChangelogWindow.xaml.cs +++ b/Content.Client/Changelog/ChangelogWindow.xaml.cs @@ -87,14 +87,12 @@ private void TabsUpdated() if (!tab.AdminOnly || isAdmin) { Tabs.SetTabVisible(i, true); - tab.Visible = true; visibleTabs++; firstVisible ??= i; } else { Tabs.SetTabVisible(i, false); - tab.Visible = false; } } diff --git a/Content.Client/Chemistry/UI/ButtonGrid.cs b/Content.Client/Chemistry/UI/ButtonGrid.cs new file mode 100644 index 00000000000..0abd9ef8a43 --- /dev/null +++ b/Content.Client/Chemistry/UI/ButtonGrid.cs @@ -0,0 +1,119 @@ +using System; +using Robust.Client.Graphics; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.Chemistry.UI; + +/// +/// Creates a grid of buttons given a comma-seperated list of Text +/// +public sealed class ButtonGrid : GridContainer +{ + private string _buttonList = ""; + + /// + /// A comma-seperated list of text to use for each button. These will be inserted sequentially. + /// + public string ButtonList + { + get => _buttonList; + set + { + _buttonList = value; + Update(); + } + } + + public bool RadioGroup { get; set; } = false; + + private string? _selected; + + /// + /// Which button is currently selected. Only matters when is true. + /// + public string? Selected + { + get => _selected; + set + { + _selected = value; + Update(); + } + } + + public Action? OnButtonPressed; + + /// + /// + /// + public new int Columns + { + get => base.Columns; + set + { + base.Columns = value; + Update(); + } + } + + /// + /// + /// + public new int Rows + { + get => base.Rows; + set + { + base.Rows = value; + Update(); + } + } + + private void Update() + { + if (ButtonList == "") + return; + + this.Children.Clear(); + var i = 0; + var list = ButtonList.Split(","); + + var group = new ButtonGroup(); + + foreach (var button in list) + { + var btn = new Button(); + btn.Text = button; + btn.OnPressed += _ => + { + if (RadioGroup) + btn.Pressed = true; + Selected = button; + OnButtonPressed?.Invoke(button); + }; + if (button == Selected) + btn.Pressed = true; + var sep = HSeparationOverride ?? 0; + // ReSharper disable once PossibleLossOfFraction + // btn.SetWidth = (this.PixelWidth - sep * (Columns - 1)) / 3; + btn.Group = group; + + var row = i / Columns; + var col = i % Columns; + var last = i == list.Length - 1; + var lastCol = i == Columns - 1; + var lastRow = row == list.Length / Columns - 1; + + if (row == 0 && (lastCol || last)) + btn.AddStyleClass("OpenLeft"); + else if (col == 0 && lastRow) + btn.AddStyleClass("OpenRight"); + else + btn.AddStyleClass("OpenBoth"); + + this.Children.Add(btn); + + i++; + } + } +} diff --git a/Content.Client/Chemistry/UI/ReagentCardControl.xaml b/Content.Client/Chemistry/UI/ReagentCardControl.xaml new file mode 100644 index 00000000000..257b0b6c52e --- /dev/null +++ b/Content.Client/Chemistry/UI/ReagentCardControl.xaml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/Content.Client/Chemistry/UI/ReagentCardControl.xaml.cs b/Content.Client/Chemistry/UI/ReagentCardControl.xaml.cs new file mode 100644 index 00000000000..f7cadcc264d --- /dev/null +++ b/Content.Client/Chemistry/UI/ReagentCardControl.xaml.cs @@ -0,0 +1,32 @@ +using Content.Shared.Chemistry; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Chemistry.UI; + +[GenerateTypedNameReferences] +public sealed partial class ReagentCardControl : Control +{ + public string StorageSlotId { get; } + public Action? OnPressed; + public Action? OnEjectButtonPressed; + + public ReagentCardControl(ReagentInventoryItem item) + { + RobustXamlLoader.Load(this); + + StorageSlotId = item.StorageSlotId; + ColorPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = item.ReagentColor }; + ReagentNameLabel.Text = item.ReagentLabel; + FillLabel.Text = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", item.Quantity));; + EjectButtonIcon.Text = Loc.GetString("reagent-dispenser-window-eject-container-button"); + + if (item.Quantity == 0.0) + MainButton.Disabled = true; + + MainButton.OnPressed += args => OnPressed?.Invoke(StorageSlotId); + EjectButton.OnPressed += args => OnEjectButtonPressed?.Invoke(StorageSlotId); + } +} diff --git a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs index 8244e3e6edb..99e5a3d3953 100644 --- a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs +++ b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs @@ -1,3 +1,4 @@ +using Content.Client.Guidebook.Components; using Content.Shared.Chemistry; using Content.Shared.Containers.ItemSlots; using JetBrains.Annotations; @@ -34,6 +35,7 @@ protected override void Open() _window = new() { Title = EntMan.GetComponent(Owner).EntityName, + HelpGuidebookIds = EntMan.GetComponent(Owner).Guides }; _window.OpenCentered(); @@ -42,38 +44,11 @@ protected override void Open() // Setup static button actions. _window.EjectButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentDispenser.OutputSlotName)); _window.ClearButton.OnPressed += _ => SendMessage(new ReagentDispenserClearContainerSolutionMessage()); - _window.DispenseButton1.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U1)); - _window.DispenseButton5.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U5)); - _window.DispenseButton10.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U10)); - _window.DispenseButton15.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U15)); - _window.DispenseButton20.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U20)); - _window.DispenseButton25.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U25)); - _window.DispenseButton30.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U30)); - _window.DispenseButton50.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U50)); - _window.DispenseButton100.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U100)); - // Setup reagent button actions. - _window.OnDispenseReagentButtonPressed += (args, button) => SendMessage(new ReagentDispenserDispenseReagentMessage(button.ReagentId)); - _window.OnDispenseReagentButtonMouseEntered += (args, button) => - { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; - _window.OnDispenseReagentButtonMouseExited += (args, button) => - { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; + _window.AmountGrid.OnButtonPressed += s => SendMessage(new ReagentDispenserSetDispenseAmountMessage(s)); - _window.OnEjectJugButtonPressed += (args, button) => SendMessage(new ItemSlotButtonPressedEvent(button.ReagentId)); - _window.OnEjectJugButtonMouseEntered += (args, button) => { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; - _window.OnEjectJugButtonMouseExited += (args, button) => { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; + _window.OnDispenseReagentButtonPressed += (id) => SendMessage(new ReagentDispenserDispenseReagentMessage(id)); + _window.OnEjectJugButtonPressed += (id) => SendMessage(new ItemSlotButtonPressedEvent(id)); } /// diff --git a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml index 3b812ba56b2..9da340f8a76 100644 --- a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml +++ b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml @@ -1,53 +1,78 @@ - - - - [GenerateTypedNameReferences] - public sealed partial class ReagentDispenserWindow : DefaultWindow + public sealed partial class ReagentDispenserWindow : FancyWindow { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; - public event Action? OnDispenseReagentButtonPressed; - public event Action? OnDispenseReagentButtonMouseEntered; - public event Action? OnDispenseReagentButtonMouseExited; - - public event Action? OnEjectJugButtonPressed; - public event Action? OnEjectJugButtonMouseEntered; - public event Action? OnEjectJugButtonMouseExited; + public event Action? OnDispenseReagentButtonPressed; + public event Action? OnEjectJugButtonPressed; /// /// Create and initialize the dispenser UI client-side. Creates the basic layout, @@ -35,44 +29,27 @@ public ReagentDispenserWindow() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); - - var dispenseAmountGroup = new ButtonGroup(); - DispenseButton1.Group = dispenseAmountGroup; - DispenseButton5.Group = dispenseAmountGroup; - DispenseButton10.Group = dispenseAmountGroup; - DispenseButton15.Group = dispenseAmountGroup; - DispenseButton20.Group = dispenseAmountGroup; - DispenseButton25.Group = dispenseAmountGroup; - DispenseButton30.Group = dispenseAmountGroup; - DispenseButton50.Group = dispenseAmountGroup; - DispenseButton100.Group = dispenseAmountGroup; } /// /// Update the button grid of reagents which can be dispensed. /// /// Reagents which can be dispensed by this dispenser - public void UpdateReagentsList(List>> inventory) + public void UpdateReagentsList(List inventory) { - if (ChemicalList == null) + if (ReagentList == null) return; - ChemicalList.Children.Clear(); + ReagentList.Children.Clear(); //Sort inventory by reagentLabel - inventory.Sort((x, y) => x.Value.Key.CompareTo(y.Value.Key)); + inventory.Sort((x, y) => x.ReagentLabel.CompareTo(y.ReagentLabel)); - foreach (KeyValuePair> entry in inventory) + foreach (var item in inventory) { - var button = new DispenseReagentButton(entry.Key, entry.Value.Key, entry.Value.Value); - button.OnPressed += args => OnDispenseReagentButtonPressed?.Invoke(args, button); - button.OnMouseEntered += args => OnDispenseReagentButtonMouseEntered?.Invoke(args, button); - button.OnMouseExited += args => OnDispenseReagentButtonMouseExited?.Invoke(args, button); - ChemicalList.AddChild(button); - var ejectButton = new EjectJugButton(entry.Key); - ejectButton.OnPressed += args => OnEjectJugButtonPressed?.Invoke(args, ejectButton); - ejectButton.OnMouseEntered += args => OnEjectJugButtonMouseEntered?.Invoke(args, ejectButton); - ejectButton.OnMouseExited += args => OnEjectJugButtonMouseExited?.Invoke(args, ejectButton); - ChemicalList.AddChild(ejectButton); + var card = new ReagentCardControl(item); + card.OnPressed += OnDispenseReagentButtonPressed; + card.OnEjectButtonPressed += OnEjectJugButtonPressed; + ReagentList.Children.Add(card); } } @@ -93,36 +70,7 @@ public void UpdateState(BoundUserInterfaceState state) ClearButton.Disabled = castState.OutputContainer is null; EjectButton.Disabled = castState.OutputContainer is null; - switch (castState.SelectedDispenseAmount) - { - case ReagentDispenserDispenseAmount.U1: - DispenseButton1.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U5: - DispenseButton5.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U10: - DispenseButton10.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U15: - DispenseButton15.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U20: - DispenseButton20.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U25: - DispenseButton25.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U30: - DispenseButton30.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U50: - DispenseButton50.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U100: - DispenseButton100.Pressed = true; - break; - } + AmountGrid.Selected = ((int)castState.SelectedDispenseAmount).ToString(); } /// @@ -137,23 +85,15 @@ public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state) if (state.OutputContainer is null) { + ContainerInfoName.Text = ""; + ContainerInfoFill.Text = ""; ContainerInfo.Children.Add(new Label { Text = Loc.GetString("reagent-dispenser-window-no-container-loaded-text") }); return; } - ContainerInfo.Children.Add(new BoxContainer // Name of the container and its fill status (Ex: 44/100u) - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new Label {Text = $"{state.OutputContainer.DisplayName}: "}, - new Label - { - Text = $"{state.OutputContainer.CurrentVolume}/{state.OutputContainer.MaxVolume}", - StyleClasses = {StyleNano.StyleClassLabelSecondaryColor} - } - } - }); + // Set Name of the container and its fill status (Ex: 44/100u) + ContainerInfoName.Text = state.OutputContainer.DisplayName; + ContainerInfoFill.Text = state.OutputContainer.CurrentVolume + "/" + state.OutputContainer.MaxVolume; foreach (var (reagent, quantity) in state.OutputContainer.Reagents!) { @@ -181,28 +121,4 @@ public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state) } } } - - public sealed class DispenseReagentButton : Button - { - public string ReagentId { get; } - - public DispenseReagentButton(string reagentId, string text, string amount) - { - AddStyleClass("OpenRight"); - ReagentId = reagentId; - Text = text + " " + amount; - } - } - - public sealed class EjectJugButton : Button - { - public string ReagentId { get; } - - public EjectJugButton(string reagentId) - { - AddStyleClass("OpenLeft"); - ReagentId = reagentId; - Text = "⏏"; - } - } } diff --git a/Content.Client/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs b/Content.Client/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs index 2a34376f6a6..4bf9d4c3e1c 100644 --- a/Content.Client/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs +++ b/Content.Client/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs @@ -2,6 +2,4 @@ namespace Content.Client.CriminalRecords.Systems; -public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleSystem -{ -} +public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleSystem; diff --git a/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs b/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs new file mode 100644 index 00000000000..c895a00c887 --- /dev/null +++ b/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.CriminalRecords.Systems; + +namespace Content.Client.CriminalRecords.Systems; + +public sealed class CriminalRecordsHackerSystem : SharedCriminalRecordsHackerSystem; diff --git a/Content.Client/CriminalRecords/Systems/CriminalRecordsSystem.cs b/Content.Client/CriminalRecords/Systems/CriminalRecordsSystem.cs new file mode 100644 index 00000000000..c0b98d7ce31 --- /dev/null +++ b/Content.Client/CriminalRecords/Systems/CriminalRecordsSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.CriminalRecords.Systems; + +namespace Content.Client.CriminalRecords.Systems; + +public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem; diff --git a/Content.Client/Decals/ToggleDecalCommand.cs b/Content.Client/Decals/ToggleDecalCommand.cs index 9f0851f0806..025ed1299d1 100644 --- a/Content.Client/Decals/ToggleDecalCommand.cs +++ b/Content.Client/Decals/ToggleDecalCommand.cs @@ -5,11 +5,13 @@ namespace Content.Client.Decals; public sealed class ToggleDecalCommand : IConsoleCommand { + [Dependency] private readonly IEntityManager _e = default!; + public string Command => "toggledecals"; public string Description => "Toggles decaloverlay"; public string Help => $"{Command}"; public void Execute(IConsoleShell shell, string argStr, string[] args) { - EntitySystem.Get().ToggleOverlay(); + _e.System().ToggleOverlay(); } } diff --git a/Content.Client/Decals/UI/DecalPlacerWindow.xaml.cs b/Content.Client/Decals/UI/DecalPlacerWindow.xaml.cs index 1be17510807..21b816515a4 100644 --- a/Content.Client/Decals/UI/DecalPlacerWindow.xaml.cs +++ b/Content.Client/Decals/UI/DecalPlacerWindow.xaml.cs @@ -16,6 +16,7 @@ namespace Content.Client.Decals.UI; public sealed partial class DecalPlacerWindow : DefaultWindow { [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IEntityManager _e = default!; private readonly DecalPlacementSystem _decalPlacementSystem; @@ -39,7 +40,7 @@ public DecalPlacerWindow() RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); - _decalPlacementSystem = EntitySystem.Get(); + _decalPlacementSystem = _e.System(); // This needs to be done in C# so we can have custom stuff passed in the constructor // and thus have a proper step size diff --git a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs index b9e4a386604..2fe56fcce99 100644 --- a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs +++ b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs @@ -153,7 +153,7 @@ private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, Sprite } } else if (state == VisualState.OverlayCharging) - sprite.LayerSetState(DisposalUnitVisualLayers.OverlayFlush, new RSI.StateId("disposal-charging")); + sprite.LayerSetState(DisposalUnitVisualLayers.OverlayFlush, chargingState); else _animationSystem.Stop(uid, AnimationKey); diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 6abf8da4230..4446256daf8 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -10,10 +10,10 @@ using Content.Client.Input; using Content.Client.IoC; using Content.Client.Launcher; +using Content.Client.Lobby; using Content.Client.MainMenu; using Content.Client.Parallax.Managers; using Content.Client.Players.PlayTimeTracking; -using Content.Client.Preferences; using Content.Client.Radiation.Overlays; using Content.Client.Replay; using Content.Client.Screenshot; diff --git a/Content.Client/GPS/UI/HandheldGpsStatusControl.cs b/Content.Client/GPS/UI/HandheldGpsStatusControl.cs index de6a1031bad..7dcf3f29c51 100644 --- a/Content.Client/GPS/UI/HandheldGpsStatusControl.cs +++ b/Content.Client/GPS/UI/HandheldGpsStatusControl.cs @@ -1,6 +1,7 @@ using Content.Client.GPS.Components; using Content.Client.Message; using Content.Client.Stylesheets; +using Robust.Client.GameObjects; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Timing; @@ -13,11 +14,13 @@ public sealed class HandheldGpsStatusControl : Control private readonly RichTextLabel _label; private float _updateDif; private readonly IEntityManager _entMan; + private readonly SharedTransformSystem _transform; public HandheldGpsStatusControl(Entity parent) { _parent = parent; _entMan = IoCManager.Resolve(); + _transform = _entMan.System(); _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } }; AddChild(_label); UpdateGpsDetails(); @@ -41,7 +44,7 @@ private void UpdateGpsDetails() var posText = "Error"; if (_entMan.TryGetComponent(_parent, out TransformComponent? transComp)) { - var pos = transComp.MapPosition; + var pos = _transform.GetMapCoordinates(_parent.Owner, xform: transComp); var x = (int) pos.X; var y = (int) pos.Y; posText = $"({x}, {y})"; diff --git a/Content.Client/Guidebook/GuidebookSystem.cs b/Content.Client/Guidebook/GuidebookSystem.cs index cb13d4ca6e5..0aa2c85142e 100644 --- a/Content.Client/Guidebook/GuidebookSystem.cs +++ b/Content.Client/Guidebook/GuidebookSystem.cs @@ -80,6 +80,11 @@ private void OnGetVerbs(EntityUid uid, GuideHelpComponent component, GetVerbsEve }); } + public void OpenHelp(List guides) + { + OnGuidebookOpen?.Invoke(guides, null, null, true, guides[0]); + } + private void OnInteract(EntityUid uid, GuideHelpComponent component, ActivateInWorldEvent args) { if (!_timing.IsFirstTimePredicted) diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index 65e95b76f08..4703915ae76 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -2,23 +2,20 @@ using Content.Client.Changelog; using Content.Client.Chat.Managers; using Content.Client.Clickable; -using Content.Client.Options; using Content.Client.Eui; using Content.Client.GhostKick; using Content.Client.Info; using Content.Client.Launcher; using Content.Client.Parallax.Managers; using Content.Client.Players.PlayTimeTracking; -using Content.Client.Preferences; using Content.Client.Screenshot; using Content.Client.Fullscreen; using Content.Client.Stylesheets; using Content.Client.Viewport; using Content.Client.Voting; -using Content.Shared.Administration; using Content.Shared.Administration.Logs; -using Content.Shared.Module; using Content.Client.Guidebook; +using Content.Client.Lobby; using Content.Client.Replay; using Content.Shared.Administration.Managers; using Content.Shared.Players.PlayTimeTracking; diff --git a/Content.Client/Preferences/ClientPreferencesManager.cs b/Content.Client/Lobby/ClientPreferencesManager.cs similarity index 97% rename from Content.Client/Preferences/ClientPreferencesManager.cs rename to Content.Client/Lobby/ClientPreferencesManager.cs index 89cee7bf79b..3f01e1a8f67 100644 --- a/Content.Client/Preferences/ClientPreferencesManager.cs +++ b/Content.Client/Lobby/ClientPreferencesManager.cs @@ -2,12 +2,10 @@ using Content.Shared.Preferences; using Robust.Client; using Robust.Client.Player; -using Robust.Shared.Configuration; using Robust.Shared.Network; -using Robust.Shared.Prototypes; using Robust.Shared.Utility; -namespace Content.Client.Preferences +namespace Content.Client.Lobby { /// /// Receives and from the server during the initial diff --git a/Content.Client/Preferences/IClientPreferencesManager.cs b/Content.Client/Lobby/IClientPreferencesManager.cs similarity index 92% rename from Content.Client/Preferences/IClientPreferencesManager.cs rename to Content.Client/Lobby/IClientPreferencesManager.cs index e55d6b600ca..45a770b1621 100644 --- a/Content.Client/Preferences/IClientPreferencesManager.cs +++ b/Content.Client/Lobby/IClientPreferencesManager.cs @@ -1,7 +1,6 @@ -using System; using Content.Shared.Preferences; -namespace Content.Client.Preferences +namespace Content.Client.Lobby { public interface IClientPreferencesManager { diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs index 91730020a4e..1aabc4ff381 100644 --- a/Content.Client/Lobby/LobbyState.cs +++ b/Content.Client/Lobby/LobbyState.cs @@ -3,8 +3,6 @@ using Content.Client.LateJoin; using Content.Client.Lobby.UI; using Content.Client.Message; -using Content.Client.Preferences; -using Content.Client.Preferences.UI; using Content.Client.UserInterface.Systems.Chat; using Content.Client.Voting; using Robust.Client; @@ -12,8 +10,6 @@ using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; -using Robust.Shared.Configuration; -using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -25,20 +21,15 @@ public sealed class LobbyState : Robust.Client.State.State [Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; - [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IVoteManager _voteManager = default!; - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - - [ViewVariables] private CharacterSetupGui? _characterSetup; private ClientGameTicker _gameTicker = default!; private ContentAudioSystem _contentAudioSystem = default!; protected override Type? LinkedScreenType { get; } = typeof(LobbyGui); - private LobbyGui? _lobby; + public LobbyGui? Lobby; protected override void Startup() { @@ -47,45 +38,23 @@ protected override void Startup() return; } - _lobby = (LobbyGui) _userInterfaceManager.ActiveScreen; + Lobby = (LobbyGui) _userInterfaceManager.ActiveScreen; var chatController = _userInterfaceManager.GetUIController(); _gameTicker = _entityManager.System(); _contentAudioSystem = _entityManager.System(); _contentAudioSystem.LobbySoundtrackChanged += UpdateLobbySoundtrackInfo; - _characterSetup = new CharacterSetupGui(_entityManager, _resourceCache, _preferencesManager, - _prototypeManager, _configurationManager); - LayoutContainer.SetAnchorPreset(_characterSetup, LayoutContainer.LayoutPreset.Wide); - _lobby.CharacterSetupState.AddChild(_characterSetup); chatController.SetMainChat(true); - _voteManager.SetPopupContainer(_lobby.VoteContainer); - - _characterSetup.CloseButton.OnPressed += _ => - { - // Reset sliders etc. - _characterSetup?.UpdateControls(); - - var controller = _userInterfaceManager.GetUIController(); - controller.SetClothes(true); - controller.UpdateProfile(); - _lobby.SwitchState(LobbyGui.LobbyGuiState.Default); - }; - - _characterSetup.SaveButton.OnPressed += _ => - { - _characterSetup.Save(); - _userInterfaceManager.GetUIController().ReloadProfile(); - }; - - LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide); - _lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you... + _voteManager.SetPopupContainer(Lobby.VoteContainer); + LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide); + Lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you... UpdateLobbyUi(); - _lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed; - _lobby.ReadyButton.OnPressed += OnReadyPressed; - _lobby.ReadyButton.OnToggled += OnReadyToggled; + Lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed; + Lobby.ReadyButton.OnPressed += OnReadyPressed; + Lobby.ReadyButton.OnToggled += OnReadyToggled; _gameTicker.InfoBlobUpdated += UpdateLobbyUi; _gameTicker.LobbyStatusUpdated += LobbyStatusUpdated; @@ -103,20 +72,23 @@ protected override void Shutdown() _voteManager.ClearPopupContainer(); - _lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed; - _lobby!.ReadyButton.OnPressed -= OnReadyPressed; - _lobby!.ReadyButton.OnToggled -= OnReadyToggled; + Lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed; + Lobby!.ReadyButton.OnPressed -= OnReadyPressed; + Lobby!.ReadyButton.OnToggled -= OnReadyToggled; - _lobby = null; + Lobby = null; + } - _characterSetup?.Dispose(); - _characterSetup = null; + public void SwitchState(LobbyGui.LobbyGuiState state) + { + // Yeah I hate this but LobbyState contains all the badness for now. + Lobby?.SwitchState(state); } private void OnSetupPressed(BaseButton.ButtonEventArgs args) { SetReady(false); - _lobby!.SwitchState(LobbyGui.LobbyGuiState.CharacterSetup); + Lobby?.SwitchState(LobbyGui.LobbyGuiState.CharacterSetup); } private void OnReadyPressed(BaseButton.ButtonEventArgs args) @@ -138,13 +110,13 @@ public override void FrameUpdate(FrameEventArgs e) { if (_gameTicker.IsGameStarted) { - _lobby!.StartTime.Text = string.Empty; + Lobby!.StartTime.Text = string.Empty; var roundTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan); - _lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-time", ("hours", roundTime.Hours), ("minutes", roundTime.Minutes)); + Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-time", ("hours", roundTime.Hours), ("minutes", roundTime.Minutes)); return; } - _lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started"); + Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started"); string text; if (_gameTicker.Paused) @@ -153,7 +125,7 @@ public override void FrameUpdate(FrameEventArgs e) } else if (_gameTicker.StartTime < _gameTiming.CurTime) { - _lobby!.StartTime.Text = Loc.GetString("lobby-state-soon"); + Lobby!.StartTime.Text = Loc.GetString("lobby-state-soon"); return; } else @@ -170,7 +142,7 @@ public override void FrameUpdate(FrameEventArgs e) } } - _lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text)); + Lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text)); } private void LobbyStatusUpdated() @@ -181,31 +153,31 @@ private void LobbyStatusUpdated() private void LobbyLateJoinStatusUpdated() { - _lobby!.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin; + Lobby!.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin; } private void UpdateLobbyUi() { if (_gameTicker.IsGameStarted) { - _lobby!.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state"); - _lobby!.ReadyButton.ToggleMode = false; - _lobby!.ReadyButton.Pressed = false; - _lobby!.ObserveButton.Disabled = false; + Lobby!.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state"); + Lobby!.ReadyButton.ToggleMode = false; + Lobby!.ReadyButton.Pressed = false; + Lobby!.ObserveButton.Disabled = false; } else { - _lobby!.StartTime.Text = string.Empty; - _lobby!.ReadyButton.Text = Loc.GetString(_lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready"); - _lobby!.ReadyButton.ToggleMode = true; - _lobby!.ReadyButton.Disabled = false; - _lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady; - _lobby!.ObserveButton.Disabled = true; + Lobby!.StartTime.Text = string.Empty; + Lobby!.ReadyButton.Text = Loc.GetString(Lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready"); + Lobby!.ReadyButton.ToggleMode = true; + Lobby!.ReadyButton.Disabled = false; + Lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady; + Lobby!.ObserveButton.Disabled = true; } if (_gameTicker.ServerInfoBlob != null) { - _lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob); + Lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob); } } @@ -213,7 +185,7 @@ private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev) { if (ev.SoundtrackFilename == null) { - _lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text")); + Lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text")); } else if ( ev.SoundtrackFilename != null @@ -234,7 +206,7 @@ private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev) ("songTitle", title), ("songArtist", artist)); - _lobby!.LobbySong.SetMarkup(markup); + Lobby!.LobbySong.SetMarkup(markup); } } @@ -242,11 +214,11 @@ private void UpdateLobbyBackground() { if (_gameTicker.LobbyBackground != null) { - _lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground ); + Lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground ); } else { - _lobby!.Background.Texture = null; + Lobby!.Background.Texture = null; } } diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index 9eb259657dc..ae9196c1100 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -2,190 +2,292 @@ using Content.Client.Humanoid; using Content.Client.Inventory; using Content.Client.Lobby.UI; -using Content.Client.Preferences; -using Content.Client.Preferences.UI; +using Content.Client.Players.PlayTimeTracking; using Content.Client.Station; +using Content.Shared.CCVar; using Content.Shared.Clothing; using Content.Shared.GameTicking; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; -using Content.Shared.Preferences.Loadouts.Effects; using Content.Shared.Roles; +using Content.Shared.Traits; +using Robust.Client.Player; +using Robust.Client.ResourceManagement; using Robust.Client.State; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Utility; namespace Content.Client.Lobby; public sealed class LobbyUIController : UIController, IOnStateEntered, IOnStateExited { [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; - [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IFileDialogManager _dialogManager = default!; + [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly JobRequirementsManager _requirements = default!; + [Dependency] private readonly MarkingManager _markings = default!; [UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!; [UISystemDependency] private readonly ClientInventorySystem _inventory = default!; [UISystemDependency] private readonly StationSpawningSystem _spawn = default!; - private LobbyCharacterPreviewPanel? _previewPanel; - - private bool _showClothes = true; - - /* - * Each character profile has its own dummy. There is also a dummy for the lobby screen + character editor - * that is shared too. - */ + private CharacterSetupGui? _characterSetup; + private HumanoidProfileEditor? _profileEditor; /// - /// Preview dummy for role gear. + /// This is the characher preview panel in the chat. This should only update if their character updates. /// - private EntityUid? _previewDummy; + private LobbyCharacterPreviewPanel? PreviewPanel => GetLobbyPreview(); /// - /// If we currently have a job prototype selected. + /// This is the modified profile currently being edited. /// - private JobPrototype? _dummyJob; - - // TODO: Load the species directly and don't update entity ever. - public event Action? PreviewDummyUpdated; + private HumanoidCharacterProfile? EditedProfile => _profileEditor?.Profile; - private HumanoidCharacterProfile? _profile; + private int? EditedSlot => _profileEditor?.CharacterSlot; public override void Initialize() { base.Initialize(); + _prototypeManager.PrototypesReloaded += OnProtoReload; _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded; - } + _requirements.Updated += OnRequirementsUpdated; - private void PreferencesDataLoaded() - { - UpdateProfile(); + _configurationManager.OnValueChanged(CCVars.FlavorText, args => + { + _profileEditor?.RefreshFlavorText(); + }); + + _configurationManager.OnValueChanged(CCVars.GameRoleTimers, args => + { + _profileEditor?.RefreshAntags(); + _profileEditor?.RefreshJobs(); + _profileEditor?.RefreshLoadouts(); + }); } - public void OnStateEntered(LobbyState state) + private LobbyCharacterPreviewPanel? GetLobbyPreview() { + if (_stateManager.CurrentState is LobbyState lobby) + { + return lobby.Lobby?.CharacterPreview; + } + + return null; } - public void OnStateExited(LobbyState state) + private void OnRequirementsUpdated() { - EntityManager.DeleteEntity(_previewDummy); - _previewDummy = null; + if (_profileEditor != null) + { + _profileEditor.RefreshAntags(); + _profileEditor.RefreshJobs(); + } } - public void SetPreviewPanel(LobbyCharacterPreviewPanel? panel) + private void OnProtoReload(PrototypesReloadedEventArgs obj) { - _previewPanel = panel; - ReloadProfile(); + if (_profileEditor != null) + { + if (obj.WasModified()) + { + _profileEditor.RefreshAntags(); + } + + if (obj.WasModified() || + obj.WasModified()) + { + _profileEditor.RefreshJobs(); + } + + if (obj.WasModified() || + obj.WasModified() || + obj.WasModified()) + { + _profileEditor.RefreshLoadouts(); + } + + if (obj.WasModified()) + { + _profileEditor.RefreshSpecies(); + } + + if (obj.WasModified()) + { + _profileEditor.RefreshTraits(); + } + } } - public void SetClothes(bool value) + private void PreferencesDataLoaded() { - if (_showClothes == value) + PreviewPanel?.SetLoaded(true); + + if (_stateManager.CurrentState is not LobbyState) return; - _showClothes = value; - ReloadCharacterUI(); + ReloadCharacterSetup(); } - public void SetDummyJob(JobPrototype? job) + public void OnStateEntered(LobbyState state) { - _dummyJob = job; - ReloadCharacterUI(); + PreviewPanel?.SetLoaded(_preferencesManager.ServerDataLoaded); + ReloadCharacterSetup(); } - /// - /// Updates the character only with the specified profile change. - /// - public void ReloadProfile() + public void OnStateExited(LobbyState state) { - // Test moment - if (_profile == null || _stateManager.CurrentState is not LobbyState) - return; + PreviewPanel?.SetLoaded(false); + _profileEditor?.Dispose(); + _characterSetup?.Dispose(); - // Ignore job clothes and the likes so we don't spam entities out every frame of color changes. - var previewDummy = EnsurePreviewDummy(_profile); - _humanoid.LoadProfile(previewDummy, _profile); + _characterSetup = null; + _profileEditor = null; } /// - /// Updates the currently selected character's preview. + /// Reloads every single character setup control. /// - public void ReloadCharacterUI() + public void ReloadCharacterSetup() { - // Test moment - if (_profile == null || _stateManager.CurrentState is not LobbyState) - return; - - EntityManager.DeleteEntity(_previewDummy); - _previewDummy = null; - _previewDummy = EnsurePreviewDummy(_profile); - _previewPanel?.SetSprite(_previewDummy.Value); - _previewPanel?.SetSummaryText(_profile.Summary); - _humanoid.LoadProfile(_previewDummy.Value, _profile); - - if (_showClothes) - GiveDummyJobClothesLoadout(_previewDummy.Value, _profile); + RefreshLobbyPreview(); + var (characterGui, profileEditor) = EnsureGui(); + characterGui.ReloadCharacterPickers(); + profileEditor.SetProfile( + (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter, + _preferencesManager.Preferences?.SelectedCharacterIndex); } /// - /// Updates character profile to the default. + /// Refreshes the character preview in the lobby chat. /// - public void UpdateProfile() + private void RefreshLobbyPreview() { - if (!_preferencesManager.ServerDataLoaded) - { - _profile = null; + if (PreviewPanel == null) return; - } - if (_preferencesManager.Preferences?.SelectedCharacter is HumanoidCharacterProfile selectedCharacter) - { - _profile = selectedCharacter; - _previewPanel?.SetLoaded(true); - } - else + // Get selected character, load it, then set it + var character = _preferencesManager.Preferences?.SelectedCharacter; + + if (character is not HumanoidCharacterProfile humanoid) { - _previewPanel?.SetSummaryText(string.Empty); - _previewPanel?.SetLoaded(false); + PreviewPanel.SetSprite(EntityUid.Invalid); + PreviewPanel.SetSummaryText(string.Empty); + return; } - ReloadCharacterUI(); + var dummy = LoadProfileEntity(humanoid, null, true); + PreviewPanel.SetSprite(dummy); + PreviewPanel.SetSummaryText(humanoid.Summary); } - public void UpdateProfile(HumanoidCharacterProfile? profile) + private void SaveProfile() { - if (_profile?.Equals(profile) == true) + DebugTools.Assert(EditedProfile != null); + + if (EditedProfile == null || EditedSlot == null) return; - if (_stateManager.CurrentState is not LobbyState) + var selected = _preferencesManager.Preferences?.SelectedCharacterIndex; + + if (selected == null) return; - _profile = profile; + _preferencesManager.UpdateCharacter(EditedProfile, EditedSlot.Value); + ReloadCharacterSetup(); } - private EntityUid EnsurePreviewDummy(HumanoidCharacterProfile profile) + private (CharacterSetupGui, HumanoidProfileEditor) EnsureGui() { - if (_previewDummy != null) - return _previewDummy.Value; + if (_characterSetup != null && _profileEditor != null) + { + _characterSetup.Visible = true; + _profileEditor.Visible = true; + return (_characterSetup, _profileEditor); + } + + _profileEditor = new HumanoidProfileEditor( + _preferencesManager, + _configurationManager, + EntityManager, + _dialogManager, + _logManager, + _playerManager, + _prototypeManager, + _requirements, + _markings); + + _characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor); + + _characterSetup.CloseButton.OnPressed += _ => + { + // Reset sliders etc. + _profileEditor.SetProfile(null, null); + _profileEditor.Visible = false; + + if (_stateManager.CurrentState is LobbyState lobbyGui) + { + lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default); + } + }; + + _profileEditor.Save += SaveProfile; + + _characterSetup.SelectCharacter += args => + { + _preferencesManager.SelectCharacter(args); + ReloadCharacterSetup(); + }; + + _characterSetup.DeleteCharacter += args => + { + _preferencesManager.DeleteCharacter(args); - _previewDummy = EntityManager.SpawnEntity(_prototypeManager.Index(profile.Species).DollPrototype, MapCoordinates.Nullspace); - PreviewDummyUpdated?.Invoke(_previewDummy.Value); - return _previewDummy.Value; + // Reload everything + if (EditedSlot == args) + { + ReloadCharacterSetup(); + } + else + { + // Only need to reload character pickers + _characterSetup?.ReloadCharacterPickers(); + } + }; + + if (_stateManager.CurrentState is LobbyState lobby) + { + lobby.Lobby?.CharacterSetupState.AddChild(_characterSetup); + } + + return (_characterSetup, _profileEditor); } + #region Helpers + /// /// Applies the highest priority job's clothes to the dummy. /// - public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile profile) + public void GiveDummyJobClothesLoadout(EntityUid dummy, JobPrototype? jobProto, HumanoidCharacterProfile profile) { - var job = _dummyJob ?? GetPreferredJob(profile); + var job = jobProto ?? GetPreferredJob(profile); GiveDummyJobClothes(dummy, profile, job); if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(job.ID))) { - var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), EntityManager, _prototypeManager); + var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), profile.Species, EntityManager, _prototypeManager); GiveDummyLoadout(dummy, loadout); } } @@ -279,8 +381,39 @@ public void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profil } } - public EntityUid? GetPreviewDummy() + /// + /// Loads the profile onto a dummy entity. + /// + public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, JobPrototype? job, bool jobClothes) { - return _previewDummy; + EntityUid dummyEnt; + + if (humanoid is not null) + { + var dummy = _prototypeManager.Index(humanoid.Species).DollPrototype; + dummyEnt = EntityManager.SpawnEntity(dummy, MapCoordinates.Nullspace); + } + else + { + dummyEnt = EntityManager.SpawnEntity(_prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace); + } + + _humanoid.LoadProfile(dummyEnt, humanoid); + + if (humanoid != null && jobClothes) + { + job ??= GetPreferredJob(humanoid); + GiveDummyJobClothes(dummyEnt, humanoid, job); + + if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(job.ID))) + { + var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), humanoid.Species, EntityManager, _prototypeManager); + GiveDummyLoadout(dummyEnt, loadout); + } + } + + return dummyEnt; } + + #endregion } diff --git a/Content.Client/Lobby/UI/CharacterPickerButton.xaml b/Content.Client/Lobby/UI/CharacterPickerButton.xaml new file mode 100644 index 00000000000..af1e640aadb --- /dev/null +++ b/Content.Client/Lobby/UI/CharacterPickerButton.xaml @@ -0,0 +1,22 @@ + + + + + [GenerateTypedNameReferences] + public sealed partial class CharacterSetupGui : Control + { + private readonly IClientPreferencesManager _preferencesManager; + private readonly IEntityManager _entManager; + private readonly IPrototypeManager _protomanager; + + private readonly Button _createNewCharacterButton; + + public event Action? SelectCharacter; + public event Action? DeleteCharacter; + + public CharacterSetupGui( + IEntityManager entManager, + IPrototypeManager protoManager, + IResourceCache resourceCache, + IClientPreferencesManager preferencesManager, + HumanoidProfileEditor profileEditor) + { + RobustXamlLoader.Load(this); + _preferencesManager = preferencesManager; + _entManager = entManager; + _protomanager = protoManager; + + var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png"); + var back = new StyleBoxTexture + { + Texture = panelTex, + Modulate = new Color(37, 37, 42) + }; + back.SetPatchMargin(StyleBox.Margin.All, 10); + + BackgroundPanel.PanelOverride = back; + + _createNewCharacterButton = new Button + { + Text = Loc.GetString("character-setup-gui-create-new-character-button"), + }; + + _createNewCharacterButton.OnPressed += args => + { + preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random()); + ReloadCharacterPickers(); + args.Event.Handle(); + }; + + CharEditor.AddChild(profileEditor); + RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open(); + + StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered(); + } + + /// + /// Disposes and reloads all character picker buttons from the preferences data. + /// + public void ReloadCharacterPickers() + { + _createNewCharacterButton.Orphan(); + Characters.DisposeAllChildren(); + + var numberOfFullSlots = 0; + var characterButtonsGroup = new ButtonGroup(); + + if (!_preferencesManager.ServerDataLoaded) + { + return; + } + + _createNewCharacterButton.ToolTip = + Loc.GetString("character-setup-gui-create-new-character-button-tooltip", + ("maxCharacters", _preferencesManager.Settings!.MaxCharacterSlots)); + + var selectedSlot = _preferencesManager.Preferences?.SelectedCharacterIndex; + + foreach (var (slot, character) in _preferencesManager.Preferences!.Characters) + { + numberOfFullSlots++; + var characterPickerButton = new CharacterPickerButton(_entManager, + _protomanager, + characterButtonsGroup, + character, + slot == selectedSlot); + + Characters.AddChild(characterPickerButton); + + characterPickerButton.OnPressed += args => + { + SelectCharacter?.Invoke(slot); + }; + + characterPickerButton.OnDeletePressed += () => + { + DeleteCharacter?.Invoke(slot); + }; + } + + _createNewCharacterButton.Disabled = numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots; + Characters.AddChild(_createNewCharacterButton); + } + } +} diff --git a/Content.Client/Preferences/UI/HighlightedContainer.xaml b/Content.Client/Lobby/UI/HighlightedContainer.xaml similarity index 100% rename from Content.Client/Preferences/UI/HighlightedContainer.xaml rename to Content.Client/Lobby/UI/HighlightedContainer.xaml diff --git a/Content.Client/Preferences/UI/HighlightedContainer.xaml.cs b/Content.Client/Lobby/UI/HighlightedContainer.xaml.cs similarity index 88% rename from Content.Client/Preferences/UI/HighlightedContainer.xaml.cs rename to Content.Client/Lobby/UI/HighlightedContainer.xaml.cs index 68294d0f059..084c1c37098 100644 --- a/Content.Client/Preferences/UI/HighlightedContainer.xaml.cs +++ b/Content.Client/Lobby/UI/HighlightedContainer.xaml.cs @@ -2,7 +2,7 @@ using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; -namespace Content.Client.Preferences.UI; +namespace Content.Client.Lobby.UI; [GenerateTypedNameReferences] public sealed partial class HighlightedContainer : PanelContainer diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml similarity index 67% rename from Content.Client/Preferences/UI/HumanoidProfileEditor.xaml rename to Content.Client/Lobby/UI/HumanoidProfileEditor.xaml index 5926aee8987..03a205e94a8 100644 --- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml @@ -1,54 +1,50 @@ - + - + - + public AlignRCDConstruction(PlacementManager pMan) : base(pMan) { - var dependencies = IoCManager.Instance!; - _entityManager = dependencies.Resolve(); - _mapManager = dependencies.Resolve(); - _playerManager = dependencies.Resolve(); - _stateManager = dependencies.Resolve(); - + IoCManager.InjectDependencies(this); _mapSystem = _entityManager.System(); _rcdSystem = _entityManager.System(); _transformSystem = _entityManager.System(); diff --git a/Content.Client/RCD/RCDMenu.xaml.cs b/Content.Client/RCD/RCDMenu.xaml.cs index 51ec66ea444..3eb0397a690 100644 --- a/Content.Client/RCD/RCDMenu.xaml.cs +++ b/Content.Client/RCD/RCDMenu.xaml.cs @@ -68,7 +68,7 @@ public RCDMenu(EntityUid owner, RCDMenuBoundUserInterface bui) tooltip = Loc.GetString(entProto.Name); } - tooltip = char.ToUpper(tooltip[0]) + tooltip.Remove(0, 1); + tooltip = OopsConcat(char.ToUpper(tooltip[0]).ToString(), tooltip.Remove(0, 1)); var button = new RCDMenuButton() { @@ -119,6 +119,12 @@ public RCDMenu(EntityUid owner, RCDMenuBoundUserInterface bui) SendRCDSystemMessageAction += bui.SendRCDSystemMessage; } + private static string OopsConcat(string a, string b) + { + // This exists to prevent Roslyn being clever and compiling something that fails sandbox checks. + return a + b; + } + private void AddRCDMenuButtonOnClickActions(Control control) { var radialContainer = control as RadialContainer; diff --git a/Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs b/Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs index 9012767ef3f..8d5607af2d0 100644 --- a/Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs +++ b/Content.Client/Radiation/Overlays/RadiationPulseOverlay.cs @@ -1,9 +1,11 @@ using System.Numerics; using Content.Shared.Radiation.Components; +using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; using Robust.Shared.Graphics; using Robust.Shared.Map; +using Robust.Shared.Physics; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -14,6 +16,7 @@ public sealed class RadiationPulseOverlay : Overlay [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + private TransformSystem? _transform; private const float MaxDist = 15.0f; @@ -72,6 +75,8 @@ protected override void Draw(in OverlayDrawArgs args) //Queries all pulses on the map and either adds or removes them from the list of rendered pulses based on whether they should be drawn (in range? on the same z-level/map? pulse entity still exists?) private void RadiationQuery(IEye? currentEye) { + _transform ??= _entityManager.System(); + if (currentEye == null) { _pulses.Clear(); @@ -91,7 +96,7 @@ private void RadiationQuery(IEye? currentEye) ( _baseShader.Duplicate(), new RadiationShaderInstance( - _entityManager.GetComponent(pulseEntity).MapPosition, + _transform.GetMapCoordinates(pulseEntity), pulse.VisualRange, pulse.StartTime, pulse.VisualDuration @@ -109,7 +114,7 @@ private void RadiationQuery(IEye? currentEye) _entityManager.TryGetComponent(pulseEntity, out RadiationPulseComponent? pulse)) { var shaderInstance = _pulses[pulseEntity]; - shaderInstance.instance.CurrentMapCoords = _entityManager.GetComponent(pulseEntity).MapPosition; + shaderInstance.instance.CurrentMapCoords = _transform.GetMapCoordinates(pulseEntity); shaderInstance.instance.Range = pulse.VisualRange; } else { _pulses[pulseEntity].shd.Dispose(); diff --git a/Content.Client/Robotics/Systems/RoboticsConsoleSystem.cs b/Content.Client/Robotics/Systems/RoboticsConsoleSystem.cs new file mode 100644 index 00000000000..0219c965cde --- /dev/null +++ b/Content.Client/Robotics/Systems/RoboticsConsoleSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Robotics.Systems; + +namespace Content.Client.Robotics.Systems; + +public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem +{ +} diff --git a/Content.Client/Robotics/UI/RoboticsConsoleBoundUserInterface.cs b/Content.Client/Robotics/UI/RoboticsConsoleBoundUserInterface.cs new file mode 100644 index 00000000000..6185979eee6 --- /dev/null +++ b/Content.Client/Robotics/UI/RoboticsConsoleBoundUserInterface.cs @@ -0,0 +1,50 @@ +using Content.Shared.Robotics; +using Robust.Client.GameObjects; + +namespace Content.Client.Robotics.UI; + +public sealed class RoboticsConsoleBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + public RoboticsConsoleWindow _window = default!; + + public RoboticsConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _window = new RoboticsConsoleWindow(Owner); + _window.OnDisablePressed += address => + { + SendMessage(new RoboticsConsoleDisableMessage(address)); + }; + _window.OnDestroyPressed += address => + { + SendMessage(new RoboticsConsoleDestroyMessage(address)); + }; + _window.OnClose += Close; + + _window.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not RoboticsConsoleState cast) + return; + + _window?.UpdateState(cast); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + _window?.Dispose(); + } +} diff --git a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml new file mode 100644 index 00000000000..a3b39787900 --- /dev/null +++ b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml @@ -0,0 +1,40 @@ + + + + + + + + + +