diff --git a/.editorconfig b/.editorconfig index 59ca35cc9a..872a068c7c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -338,7 +338,10 @@ dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter resharper_braces_for_ifelse = required_for_multiline resharper_keep_existing_attribute_arrangement = true -[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets}] +[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}] +indent_size = 2 + +[nuget.config] indent_size = 2 [{*.yaml,*.yml}] diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1ff4c49d90..4ba70f3504 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Install dependencies - run: sudo apt-get install -y python3-paramiko + run: sudo apt-get install -y python3-paramiko python3-lxml - uses: actions/checkout@v3.6.0 with: diff --git a/.vscode/launch.json b/.vscode/launch.json index 4d7ba6748e..5390b91409 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,6 +13,15 @@ "console": "internalConsole", "stopAtEntry": false }, + { + "name": "Client (Compatibility renderer)", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/bin/Content.Client/Content.Client.dll", + "args": "--cvar display.compat=true", + "console": "internalConsole", + "stopAtEntry": false + }, { "name": "Server", "type": "coreclr", diff --git a/Content.Benchmarks/Content.Benchmarks.csproj b/Content.Benchmarks/Content.Benchmarks.csproj index 049d6f5b6f..c3b60a1c69 100644 --- a/Content.Benchmarks/Content.Benchmarks.csproj +++ b/Content.Benchmarks/Content.Benchmarks.csproj @@ -11,7 +11,7 @@ 12 - + diff --git a/Content.Benchmarks/EntityManagerGetAllComponents.cs b/Content.Benchmarks/EntityManagerGetAllComponents.cs index 14edf54643..0b9683a4ab 100644 --- a/Content.Benchmarks/EntityManagerGetAllComponents.cs +++ b/Content.Benchmarks/EntityManagerGetAllComponents.cs @@ -48,6 +48,7 @@ public void Setup() var componentFactory = new Mock(); componentFactory.Setup(p => p.GetComponent()).Returns(new DummyComponent()); componentFactory.Setup(p => p.GetRegistration(It.IsAny())).Returns(dummyReg); + componentFactory.Setup(p => p.GetAllRegistrations()).Returns(new[] { dummyReg }); componentFactory.Setup(p => p.GetAllRefTypes()).Returns(new[] { CompIdx.Index() }); IoCManager.RegisterInstance(componentFactory.Object); diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs index 5d94ef85cb..7caa995836 100644 --- a/Content.Benchmarks/MapLoadBenchmark.cs +++ b/Content.Benchmarks/MapLoadBenchmark.cs @@ -46,7 +46,7 @@ public async Task Cleanup() PoolManager.Shutdown(); } - public static IEnumerable MapsSource { get; set; } + public static readonly string[] MapsSource = { "Empty", "Box", "Aspid", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Gemini", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry" }; [ParamsSource(nameof(MapsSource))] public string Map; diff --git a/Content.Benchmarks/Program.cs b/Content.Benchmarks/Program.cs index 65b5abaf73..0beb0a613d 100644 --- a/Content.Benchmarks/Program.cs +++ b/Content.Benchmarks/Program.cs @@ -23,13 +23,6 @@ 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.Server.ResolveDependency().EnumeratePrototypes().ToList(); - MapLoadBenchmark.MapsSource = gameMaps.Select(x => x.ID); - await pair.CleanReturnAsync(); - PoolManager.Shutdown(); - #if DEBUG Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("\nWARNING: YOU ARE RUNNING A DEBUG BUILD, USE A RELEASE BUILD FOR AN ACCURATE BENCHMARK"); diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs new file mode 100644 index 0000000000..c7f22bdb0c --- /dev/null +++ b/Content.Benchmarks/PvsBenchmark.cs @@ -0,0 +1,187 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Attributes; +using Content.IntegrationTests; +using Content.IntegrationTests.Pair; +using Content.Server.Warps; +using Robust.Server.GameObjects; +using Robust.Shared; +using Robust.Shared.Analyzers; +using Robust.Shared.Enums; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Random; + +namespace Content.Benchmarks; + +// This benchmark probably benefits from some accidental cache locality. I,e. the order in which entities in a pvs +// chunk are sent to players matches the order in which the entities were spawned. +// +// in a real mid-late game round, this is probably no longer the case. +// One way to somewhat offset this is to update the NetEntity assignment to assign random (but still unique) NetEntity uids to entities. +// This makes the benchmark run noticeably slower. + +[Virtual] +public class PvsBenchmark +{ + public const string Map = "Maps/box.yml"; + + [Params(1, 8, 80)] + public int PlayerCount { get; set; } + + private TestPair _pair = default!; + private IEntityManager _entMan = default!; + private MapId _mapId = new(10); + private ICommonSession[] _players = default!; + private EntityCoordinates[] _spawns = default!; + public int _cycleOffset = 0; + private SharedTransformSystem _sys = default!; + private EntityCoordinates[] _locations = default!; + + [GlobalSetup] + public void Setup() + { +#if !DEBUG + ProgramShared.PathOffset = "../../../../"; +#endif + PoolManager.Startup(null); + + _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); + _entMan = _pair.Server.ResolveDependency(); + _pair.Server.CfgMan.SetCVar(CVars.NetPVS, true); + _pair.Server.CfgMan.SetCVar(CVars.ThreadParallelCount, 0); + _pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false); + _sys = _entMan.System(); + + // Spawn the map + _pair.Server.ResolveDependency().SetSeed(42); + _pair.Server.WaitPost(() => + { + var success = _entMan.System().TryLoad(_mapId, Map, out _); + if (!success) + throw new Exception("Map load failed"); + _pair.Server.MapMan.DoMapInitialize(_mapId); + }).Wait(); + + // Get list of ghost warp positions + _spawns = _entMan.AllComponentsList() + .OrderBy(x => x.Component.Location) + .Select(x => _entMan.GetComponent(x.Uid).Coordinates) + .ToArray(); + + Array.Resize(ref _players, PlayerCount); + + // Spawn "Players". + _pair.Server.WaitPost(() => + { + for (var i = 0; i < PlayerCount; i++) + { + var pos = _spawns[i % _spawns.Length]; + var uid =_entMan.SpawnEntity("MobHuman", pos); + _pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear"); + _players[i] = new DummySession{AttachedEntity = uid}; + } + }).Wait(); + + // Repeatedly move players around so that they "explore" the map and see lots of entities. + // This will populate their PVS data with out-of-view entities. + var rng = new Random(42); + ShufflePlayers(rng, 100); + + _pair.Server.PvsTick(_players); + _pair.Server.PvsTick(_players); + + var ents = _players.Select(x => x.AttachedEntity!.Value).ToArray(); + _locations = ents.Select(x => _entMan.GetComponent(x).Coordinates).ToArray(); + } + + private void ShufflePlayers(Random rng, int count) + { + while (count > 0) + { + ShufflePlayers(rng); + count--; + } + } + + private void ShufflePlayers(Random rng) + { + _pair.Server.PvsTick(_players); + + var ents = _players.Select(x => x.AttachedEntity!.Value).ToArray(); + var locations = ents.Select(x => _entMan.GetComponent(x).Coordinates).ToArray(); + + // Shuffle locations + var n = locations.Length; + while (n > 1) + { + n -= 1; + var k = rng.Next(n + 1); + (locations[k], locations[n]) = (locations[n], locations[k]); + } + + _pair.Server.WaitPost(() => + { + for (var i = 0; i < PlayerCount; i++) + { + _sys.SetCoordinates(ents[i], locations[i]); + } + }).Wait(); + + _pair.Server.PvsTick(_players); + } + + /// + /// Basic benchmark for PVS in a static situation where nothing moves or gets dirtied.. + /// This effectively provides a lower bound on "real" pvs tick time, as it is missing: + /// - PVS chunks getting dirtied and needing to be rebuilt + /// - Fetching component states for dirty components + /// - Compressing & sending network messages + /// - Sending PVS leave messages + /// + [Benchmark] + public void StaticTick() + { + _pair.Server.PvsTick(_players); + } + + /// + /// Basic benchmark for PVS in a situation where players are teleporting all over the place. This isn't very + /// realistic, but unlike this will actually also measure the speed of processing dirty + /// chunks and sending PVS leave messages. + /// + [Benchmark] + public void CycleTick() + { + _cycleOffset = (_cycleOffset + 1) % _players.Length; + _pair.Server.WaitPost(() => + { + for (var i = 0; i < PlayerCount; i++) + { + _sys.SetCoordinates(_players[i].AttachedEntity!.Value, _locations[(i + _cycleOffset) % _players.Length]); + } + }).Wait(); + _pair.Server.PvsTick(_players); + } + + private sealed class DummySession : ICommonSession + { + public SessionStatus Status => SessionStatus.InGame; + public EntityUid? AttachedEntity {get; set; } + public NetUserId UserId => default; + public string Name => string.Empty; + public short Ping => default; + public INetChannel Channel { get; set; } = default!; + public LoginType AuthType => default; + public HashSet ViewSubscriptions { get; } = new(); + public DateTime ConnectedTime { get; set; } + public SessionState State => default!; + public SessionData Data => default!; + public bool ClientSide { get; set; } + } +} diff --git a/Content.Client/Administration/QuickDialogSystem.cs b/Content.Client/Administration/QuickDialogSystem.cs index 69a9218267..84236c5a3e 100644 --- a/Content.Client/Administration/QuickDialogSystem.cs +++ b/Content.Client/Administration/QuickDialogSystem.cs @@ -6,6 +6,8 @@ namespace Content.Client.Administration; +// mfw they ported input() from BYOND + /// /// This handles the client portion of quick dialogs. /// @@ -32,39 +34,48 @@ private void OpenDialog(QuickDialogOpenEvent ev) var promptsDict = new Dictionary(); - foreach (var entry in ev.Prompts) + for (var index = 0; index < ev.Prompts.Count; index++) { + var entry = ev.Prompts[index]; var entryBox = new BoxContainer() { Orientation = BoxContainer.LayoutOrientation.Horizontal }; entryBox.AddChild(new Label { Text = entry.Prompt, HorizontalExpand = true, SizeFlagsStretchRatio = 0.5f }); - var edit = new LineEdit() { HorizontalExpand = true}; + var edit = new LineEdit() { HorizontalExpand = true }; entryBox.AddChild(edit); switch (entry.Type) { case QuickDialogEntryType.Integer: edit.IsValid += VerifyInt; - edit.PlaceHolder = "Integer.."; + edit.PlaceHolder = Loc.GetString("quick-dialog-ui-integer"); break; case QuickDialogEntryType.Float: edit.IsValid += VerifyFloat; - edit.PlaceHolder = "Float.."; + edit.PlaceHolder = Loc.GetString("quick-dialog-ui-float"); break; case QuickDialogEntryType.ShortText: edit.IsValid += VerifyShortText; - edit.PlaceHolder = "Short text.."; + edit.PlaceHolder = Loc.GetString("quick-dialog-ui-short-text"); break; case QuickDialogEntryType.LongText: edit.IsValid += VerifyLongText; - edit.PlaceHolder = "Long text.."; + edit.PlaceHolder = Loc.GetString("quick-dialog-ui-long-text"); break; default: throw new ArgumentOutOfRangeException(); } + promptsDict.Add(entry.FieldId, edit); entryContainer.AddChild(entryBox); + + if (index == ev.Prompts.Count - 1) + { + // Last text box gets enter confirmation. + // Only the last so you don't accidentally confirm early. + edit.OnTextEntered += _ => Confirm(); + } } var buttonsBox = new BoxContainer() @@ -79,17 +90,10 @@ private void OpenDialog(QuickDialogOpenEvent ev) { var okButton = new Button() { - Text = "Ok", + Text = Loc.GetString("quick-dialog-ui-ok"), }; - okButton.OnPressed += _ => - { - RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId, - promptsDict.Select(x => (x.Key, x.Value.Text)).ToDictionary(x => x.Key, x => x.Text), - QuickDialogButtonFlag.OkButton)); - alreadyReplied = true; - window.Close(); - }; + okButton.OnPressed += _ => Confirm(); buttonsBox.AddChild(okButton); } @@ -98,7 +102,7 @@ private void OpenDialog(QuickDialogOpenEvent ev) { var cancelButton = new Button() { - Text = "Cancel", + Text = Loc.GetString("quick-dialog-ui-cancel"), }; cancelButton.OnPressed += _ => @@ -130,6 +134,17 @@ private void OpenDialog(QuickDialogOpenEvent ev) window.MinWidth *= 2; // Just double it. window.OpenCentered(); + + return; + + void Confirm() + { + RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId, + promptsDict.Select(x => (x.Key, x.Value.Text)).ToDictionary(x => x.Key, x => x.Text), + QuickDialogButtonFlag.OkButton)); + alreadyReplied = true; + window.Close(); + } } private bool VerifyInt(string input) diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml index 28b55f987f..e5269c027a 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml +++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml @@ -14,7 +14,7 @@ + + diff --git a/Content.Client/Bed/Cryostorage/CryostorageSlotControl.xaml.cs b/Content.Client/Bed/Cryostorage/CryostorageSlotControl.xaml.cs new file mode 100644 index 0000000000..629b958262 --- /dev/null +++ b/Content.Client/Bed/Cryostorage/CryostorageSlotControl.xaml.cs @@ -0,0 +1,18 @@ +using Content.Client.Message; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Bed.Cryostorage; + +[GenerateTypedNameReferences] +public sealed partial class CryostorageSlotControl : BoxContainer +{ + public CryostorageSlotControl(string name, string itemName) + { + RobustXamlLoader.Load(this); + + SlotLabel.SetMarkup(Loc.GetString("cryostorage-ui-label-slot-name", ("slot", name))); + ItemLabel.Text = itemName; + } +} diff --git a/Content.Client/Bed/Cryostorage/CryostorageSystem.cs b/Content.Client/Bed/Cryostorage/CryostorageSystem.cs new file mode 100644 index 0000000000..882f433841 --- /dev/null +++ b/Content.Client/Bed/Cryostorage/CryostorageSystem.cs @@ -0,0 +1,9 @@ +using Content.Shared.Bed.Cryostorage; + +namespace Content.Client.Bed.Cryostorage; + +/// +public sealed class CryostorageSystem : SharedCryostorageSystem +{ + +} diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index db5fa2bd99..8536c3c429 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -1,6 +1,7 @@ using Content.Client.Rotation; using Content.Shared.Buckle; using Content.Shared.Buckle.Components; +using Content.Shared.Rotation; using Content.Shared.Vehicle.Components; using Robust.Client.GameObjects; @@ -56,17 +57,15 @@ private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref Ap if (!TryComp(uid, out var rotVisuals)) return; - if (!Appearance.TryGetData(uid, StrapVisuals.RotationAngle, out var angle, args.Component) || - !Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) || + if (!Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) || !buckled || args.Sprite == null) { - _rotationVisualizerSystem.SetHorizontalAngle(uid, rotVisuals.DefaultRotation, rotVisuals); + _rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation); return; } // Animate strapping yourself to something at a given angle - _rotationVisualizerSystem.SetHorizontalAngle(uid, Angle.FromDegrees(angle), rotVisuals); // TODO: Dump this when buckle is better _rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f); } diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs index 669f64fae4..482acb3c87 100644 --- a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs +++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs @@ -1,7 +1,6 @@ using Content.Client.Cargo.UI; using Content.Shared.Cargo.Components; using JetBrains.Annotations; -using Robust.Client.GameObjects; namespace Content.Client.Cargo.BUI; diff --git a/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs index 6c9af85a2c..0be3ebd97f 100644 --- a/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs +++ b/Content.Client/Cargo/BUI/CargoOrderConsoleBoundUserInterface.cs @@ -50,8 +50,9 @@ protected override void Open() base.Open(); var spriteSystem = EntMan.System(); - _menu = new CargoConsoleMenu(IoCManager.Resolve(), spriteSystem); - var localPlayer = IoCManager.Resolve()?.LocalPlayer?.ControlledEntity; + var dependencies = IoCManager.Instance!; + _menu = new CargoConsoleMenu(Owner, EntMan, dependencies.Resolve(), spriteSystem); + var localPlayer = dependencies.Resolve().LocalEntity; var description = new FormattedMessage(); string orderRequester; diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml b/Content.Client/Cargo/UI/BountyEntry.xaml index e570b03746..60446327b3 100644 --- a/Content.Client/Cargo/UI/BountyEntry.xaml +++ b/Content.Client/Cargo/UI/BountyEntry.xaml @@ -8,14 +8,13 @@ HorizontalExpand="True"> - + - - - + diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml.cs b/Content.Client/Lathe/UI/LatheMenu.xaml.cs index 71b45fc7dc..93d80dbf9c 100644 --- a/Content.Client/Lathe/UI/LatheMenu.xaml.cs +++ b/Content.Client/Lathe/UI/LatheMenu.xaml.cs @@ -1,6 +1,8 @@ using System.Linq; using System.Text; +using Content.Client.Materials; using Content.Shared.Lathe; +using Content.Shared.Lathe.Prototypes; using Content.Shared.Materials; using Content.Shared.Research.Prototypes; using Robust.Client.AutoGenerated; @@ -17,38 +19,43 @@ public sealed partial class LatheMenu : DefaultWindow { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + private EntityUid _owner; private readonly SpriteSystem _spriteSystem; private readonly LatheSystem _lathe; + private readonly MaterialStorageSystem _materialStorage; public event Action? OnServerListButtonPressed; public event Action? RecipeQueueAction; - public event Action? OnEjectPressed; + public List> Recipes = new(); - /// - /// Default volume for a sheet if the material's entity prototype has no material composition. - /// - private const int DEFAULT_SHEET_VOLUME = 100; + public List>? Categories; + + public ProtoId? CurrentCategory; public LatheMenu(LatheBoundUserInterface owner) { + _owner = owner.Owner; RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); _spriteSystem = _entityManager.System(); _lathe = _entityManager.System(); + _materialStorage = _entityManager.System(); Title = _entityManager.GetComponent(owner.Owner).EntityName; SearchBar.OnTextChanged += _ => { - PopulateRecipes(owner.Owner); + PopulateRecipes(); }; AmountLineEdit.OnTextChanged += _ => { - PopulateRecipes(owner.Owner); + PopulateRecipes(); }; + FilterOption.OnItemSelected += OnItemSelected; + ServerListButton.OnPressed += a => OnServerListButtonPressed?.Invoke(a); if (_entityManager.TryGetComponent(owner.Owner, out var latheComponent)) @@ -58,57 +65,16 @@ public LatheMenu(LatheBoundUserInterface owner) ServerListButton.Visible = false; } } - } - - public void PopulateMaterials(EntityUid lathe) - { - if (!_entityManager.TryGetComponent(lathe, out var materials)) - return; - MaterialsList.DisposeAllChildren(); - - foreach (var (materialId, volume) in materials.Storage) - { - if (volume <= 0) - continue; - - if (!_prototypeManager.TryIndex(materialId, out MaterialPrototype? material)) - continue; - - var sheetVolume = SheetVolume(material); - var sheets = (float) volume / sheetVolume; - var maxEjectableSheets = (int) MathF.Floor(sheets); - - var unit = Loc.GetString(material.Unit); - var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit)); - var name = Loc.GetString(material.Name); - var mat = Loc.GetString("lathe-menu-material-display", ("material", name), ("amount", amountText)); - - var row = new LatheMaterialEjector(materialId, OnEjectPressed, sheetVolume, maxEjectableSheets) - { - Icon = { Texture = _spriteSystem.Frame0(material.Icon) }, - ProductName = { Text = mat } - }; - - MaterialsList.AddChild(row); - } - - if (MaterialsList.ChildCount == 0) - { - var noMaterialsMsg = Loc.GetString("lathe-menu-no-materials-message"); - var noItemRow = new Label(); - noItemRow.Text = noMaterialsMsg; - noItemRow.Align = Label.AlignMode.Center; - MaterialsList.AddChild(noItemRow); - } + MaterialsList.SetOwner(owner.Owner); } + /// /// Populates the list of all the recipes /// - /// - public void PopulateRecipes(EntityUid lathe) + public void PopulateRecipes() { - if (!_entityManager.TryGetComponent(lathe, out var component)) + if (!_entityManager.TryGetComponent(_owner, out var component)) return; var recipesToShow = new List(); @@ -117,6 +83,9 @@ public void PopulateRecipes(EntityUid lathe) if (!_prototypeManager.TryIndex(recipe, out var proto)) continue; + if (CurrentCategory != null && proto.Category != CurrentCategory) + continue; + if (SearchBar.Text.Trim().Length != 0) { if (proto.Name.ToLowerInvariant().Contains(SearchBar.Text.Trim().ToLowerInvariant())) @@ -131,8 +100,9 @@ public void PopulateRecipes(EntityUid lathe) if (!int.TryParse(AmountLineEdit.Text, out var quantity) || quantity <= 0) quantity = 1; + var sortedRecipesToShow = recipesToShow.OrderBy(p => p.Name); RecipeList.Children.Clear(); - foreach (var prototype in recipesToShow) + foreach (var prototype in sortedRecipesToShow) { StringBuilder sb = new(); var first = true; @@ -147,7 +117,7 @@ public void PopulateRecipes(EntityUid lathe) sb.Append('\n'); var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, component.MaterialUseMultiplier); - var sheetVolume = SheetVolume(proto); + var sheetVolume = _materialStorage.GetSheetVolume(proto); var unit = Loc.GetString(proto.Unit); // rounded in locale not here @@ -157,13 +127,16 @@ public void PopulateRecipes(EntityUid lathe) sb.Append(Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText))); } - sb.Append('\n'); - sb.Append(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description))); + if (!string.IsNullOrWhiteSpace(prototype.Description)) + { + sb.Append('\n'); + sb.Append(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description))); + } var icon = prototype.Icon == null ? _spriteSystem.GetPrototypeIcon(prototype.Result).Default : _spriteSystem.Frame0(prototype.Icon); - var canProduce = _lathe.CanProduce(lathe, prototype, quantity); + var canProduce = _lathe.CanProduce(_owner, prototype, quantity); var control = new RecipeControl(prototype, sb.ToString(), canProduce, icon); control.OnButtonPressed += s => @@ -176,6 +149,41 @@ public void PopulateRecipes(EntityUid lathe) } } + public void UpdateCategories() + { + var currentCategories = new List>(); + foreach (var recipeId in Recipes) + { + var recipe = _prototypeManager.Index(recipeId); + + if (recipe.Category == null) + continue; + + if (currentCategories.Contains(recipe.Category.Value)) + continue; + + currentCategories.Add(recipe.Category.Value); + } + + if (Categories != null && (Categories.Count == currentCategories.Count || !Categories.All(currentCategories.Contains))) + return; + + Categories = currentCategories; + var sortedCategories = currentCategories + .Select(p => _prototypeManager.Index(p)) + .OrderBy(p => Loc.GetString(p.Name)) + .ToList(); + + FilterOption.Clear(); + FilterOption.AddItem(Loc.GetString("lathe-menu-category-all"), -1); + foreach (var category in sortedCategories) + { + FilterOption.AddItem(Loc.GetString(category.Name), Categories.IndexOf(category.ID)); + } + + FilterOption.SelectId(-1); + } + /// /// Populates the build queue list with all queued items /// @@ -205,16 +213,17 @@ public void SetQueueInfo(LatheRecipePrototype? recipe) NameLabel.Text = $"{recipe.Name}"; } - private int SheetVolume(MaterialPrototype material) + private void OnItemSelected(OptionButton.ItemSelectedEventArgs obj) { - if (material.StackEntity == null) - return DEFAULT_SHEET_VOLUME; - - var proto = _prototypeManager.Index(material.StackEntity); - - if (!proto.TryGetComponent(out var composition)) - return DEFAULT_SHEET_VOLUME; - - return composition.MaterialComposition.FirstOrDefault(kvp => kvp.Key == material.ID).Value; + FilterOption.SelectId(obj.Id); + if (obj.Id == -1) + { + CurrentCategory = null; + } + else + { + CurrentCategory = Categories?[obj.Id]; + } + PopulateRecipes(); } } diff --git a/Content.Client/Lathe/UI/RecipeTooltip.xaml.cs b/Content.Client/Lathe/UI/RecipeTooltip.xaml.cs index 5d2e3ca081..45d854e92f 100644 --- a/Content.Client/Lathe/UI/RecipeTooltip.xaml.cs +++ b/Content.Client/Lathe/UI/RecipeTooltip.xaml.cs @@ -1,20 +1,17 @@ -using Content.Shared.Research.Prototypes; +using Content.Client.Message; using Robust.Client.AutoGenerated; -using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.XAML; -using Robust.Shared.Graphics; namespace Content.Client.Lathe.UI; [GenerateTypedNameReferences] public sealed partial class RecipeTooltip : Control { - public RecipeTooltip(string tooltip) { RobustXamlLoader.Load(this); - RecipeTooltipLabel.SetMessage(tooltip); + RecipeTooltipLabel.SetMarkup(tooltip); } } diff --git a/Content.Client/Launcher/LauncherConnecting.cs b/Content.Client/Launcher/LauncherConnecting.cs index 101a63a800..9625f9e31c 100644 --- a/Content.Client/Launcher/LauncherConnecting.cs +++ b/Content.Client/Launcher/LauncherConnecting.cs @@ -1,9 +1,12 @@ using System; using Robust.Client; using Robust.Client.UserInterface; +using Robust.Shared.Configuration; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Network; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; namespace Content.Client.Launcher { @@ -13,6 +16,9 @@ public sealed class LauncherConnecting : Robust.Client.State.State [Dependency] private readonly IClientNetManager _clientNetManager = default!; [Dependency] private readonly IGameController _gameController = default!; [Dependency] private readonly IBaseClient _baseClient = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; private LauncherConnectingGui? _control; @@ -51,7 +57,7 @@ private set protected override void Startup() { - _control = new LauncherConnectingGui(this); + _control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg); _userInterfaceManager.StateRoot.AddChild(_control); diff --git a/Content.Client/Launcher/LauncherConnectingGui.xaml b/Content.Client/Launcher/LauncherConnectingGui.xaml index 430ae58d6e..083e4ca871 100644 --- a/Content.Client/Launcher/LauncherConnectingGui.xaml +++ b/Content.Client/Launcher/LauncherConnectingGui.xaml @@ -54,4 +54,17 @@ + + + + + + + + + + + + diff --git a/Content.Client/Launcher/LauncherConnectingGui.xaml.cs b/Content.Client/Launcher/LauncherConnectingGui.xaml.cs index 8a34da6898..79fba7c285 100644 --- a/Content.Client/Launcher/LauncherConnectingGui.xaml.cs +++ b/Content.Client/Launcher/LauncherConnectingGui.xaml.cs @@ -1,12 +1,18 @@ +using System.Linq; using Content.Client.Stylesheets; +using Content.Shared.CCVar; +using Content.Shared.Dataset; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; +using Robust.Shared.Configuration; using Robust.Shared.IoC; using Robust.Shared.Timing; using Robust.Shared.Localization; using Robust.Shared.Network; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; namespace Content.Client.Launcher { @@ -15,11 +21,19 @@ public sealed partial class LauncherConnectingGui : Control { private const float RedialWaitTimeSeconds = 15f; private readonly LauncherConnecting _state; + private readonly IRobustRandom _random; + private readonly IPrototypeManager _prototype; + private readonly IConfigurationManager _cfg; + private float _redialWaitTime = RedialWaitTimeSeconds; - public LauncherConnectingGui(LauncherConnecting state) + public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random, + IPrototypeManager prototype, IConfigurationManager config) { _state = state; + _random = random; + _prototype = prototype; + _cfg = config; RobustXamlLoader.Load(this); @@ -27,6 +41,7 @@ public LauncherConnectingGui(LauncherConnecting state) Stylesheet = IoCManager.Resolve().SheetSpace; + ChangeLoginTip(); ReconnectButton.OnPressed += _ => _state.RetryConnect(); // Redial shouldn't fail, but if it does, try a reconnect (maybe we're being run from debug) RedialButton.OnPressed += _ => @@ -67,6 +82,29 @@ private void LastNetDisconnectedArgsChanged(NetDisconnectedArgs? args) ReconnectButton.Visible = !redialFlag; } + private void ChangeLoginTip() + { + var tipsDataset = _cfg.GetCVar(CCVars.LoginTipsDataset); + var loginTipsEnabled = _prototype.TryIndex(tipsDataset, out var tips); + + LoginTips.Visible = loginTipsEnabled; + if (!loginTipsEnabled) + { + return; + } + + var tipList = tips!.Values; + + if (tipList.Count == 0) + return; + + var randomIndex = _random.Next(tipList.Count); + var tip = tipList[randomIndex]; + LoginTip.SetMessage(tip); + + LoginTipTitle.Text = Loc.GetString("connecting-window-tip", ("numberTip", randomIndex)); + } + protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); diff --git a/Content.Client/Light/HandheldLightSystem.cs b/Content.Client/Light/HandheldLightSystem.cs index 1912fe91f0..7f18223811 100644 --- a/Content.Client/Light/HandheldLightSystem.cs +++ b/Content.Client/Light/HandheldLightSystem.cs @@ -17,15 +17,10 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnGetStatusControl); + Subs.ItemStatus(ent => new HandheldLightStatus(ent)); SubscribeLocalEvent(OnAppearanceChange); } - private static void OnGetStatusControl(EntityUid uid, HandheldLightComponent component, ItemStatusCollectMessage args) - { - args.Controls.Add(new HandheldLightStatus(component)); - } - private void OnAppearanceChange(EntityUid uid, HandheldLightComponent? component, ref AppearanceChangeEvent args) { if (!Resolve(uid, ref component)) diff --git a/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs b/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs index e7fcf7e219..2dc7f5a822 100644 --- a/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs +++ b/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs @@ -30,6 +30,16 @@ protected override void OnAppearanceChange(EntityUid uid, PoweredLightVisualsCom if (comp.SpriteStateMap.TryGetValue(state, out var spriteState)) args.Sprite.LayerSetState(PoweredLightLayers.Base, spriteState); + if (args.Sprite.LayerExists(PoweredLightLayers.Glow)) + { + if (TryComp(uid, out var light)) + { + args.Sprite.LayerSetColor(PoweredLightLayers.Glow, light.Color); + } + + args.Sprite.LayerSetVisible(PoweredLightLayers.Glow, state == PoweredLightState.On); + } + SetBlinkingAnimation( uid, state == PoweredLightState.On diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs index 86989cd561..f9481caa3b 100644 --- a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs +++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs @@ -148,7 +148,8 @@ public static void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile foreach (var slot in slots) { var itemType = gear.GetGear(slot.Name, profile); - if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, true, true)) + + if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false)) { entMan.DeleteEntity(unequippedItem.Value); } diff --git a/Content.Client/MagicMirror/MagicMirrorBoundUserInterface.cs b/Content.Client/MagicMirror/MagicMirrorBoundUserInterface.cs index ebb6780853..bfbf2efe4f 100644 --- a/Content.Client/MagicMirror/MagicMirrorBoundUserInterface.cs +++ b/Content.Client/MagicMirror/MagicMirrorBoundUserInterface.cs @@ -30,6 +30,7 @@ protected override void Open() _window.OnFacialHairSlotAdded += delegate () { AddSlot(MagicMirrorCategory.FacialHair); }; _window.OnFacialHairSlotRemoved += args => RemoveSlot(MagicMirrorCategory.FacialHair, args); + _window.OnClose += Close; _window.OpenCentered(); } @@ -53,17 +54,18 @@ private void AddSlot(MagicMirrorCategory category) SendMessage(new MagicMirrorAddSlotMessage(category)); } - protected override void ReceiveMessage(BoundUserInterfaceMessage message) + protected override void UpdateState(BoundUserInterfaceState state) { - base.ReceiveMessage(message); + base.UpdateState(state); - if (message is not MagicMirrorUiData data || _window == null) + if (state is not MagicMirrorUiState data || _window == null) { return; } _window.UpdateState(data); } + protected override void Dispose(bool disposing) { base.Dispose(disposing); diff --git a/Content.Client/MagicMirror/MagicMirrorWindow.xaml.cs b/Content.Client/MagicMirror/MagicMirrorWindow.xaml.cs index 802f766341..e7f5ec343f 100644 --- a/Content.Client/MagicMirror/MagicMirrorWindow.xaml.cs +++ b/Content.Client/MagicMirror/MagicMirrorWindow.xaml.cs @@ -21,8 +21,6 @@ public sealed partial class MagicMirrorWindow : DefaultWindow public Action? OnFacialHairSlotRemoved; public Action? OnFacialHairSlotAdded; - private bool _noHair; - public MagicMirrorWindow() { RobustXamlLoader.Load(this); @@ -38,15 +36,14 @@ public MagicMirrorWindow() FacialHairPicker.OnSlotAdd += delegate { OnFacialHairSlotAdded!(); }; } - public void UpdateState(MagicMirrorUiData state) + public void UpdateState(MagicMirrorUiState state) { HairPicker.UpdateData(state.Hair, state.Species, state.HairSlotTotal); FacialHairPicker.UpdateData(state.FacialHair, state.Species, state.FacialHairSlotTotal); - if (!HairPicker.Visible && !FacialHairPicker.Visible && !_noHair) + if (!HairPicker.Visible && !FacialHairPicker.Visible) { AddChild(new Label { Text = Loc.GetString("magic-mirror-component-activate-user-has-no-hair") }); - _noHair = true; } } } diff --git a/Content.Client/MassMedia/NewsSystem.cs b/Content.Client/MassMedia/NewsSystem.cs new file mode 100644 index 0000000000..59c5197091 --- /dev/null +++ b/Content.Client/MassMedia/NewsSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.MassMedia.Systems; + +namespace Content.Client.MassMedia; + +public sealed class NewsSystem : SharedNewsSystem +{ + +} diff --git a/Content.Client/MassMedia/Ui/NewsWriteBoundUserInterface.cs b/Content.Client/MassMedia/Ui/NewsWriteBoundUserInterface.cs index 975dfa3f81..d1d61d5adb 100644 --- a/Content.Client/MassMedia/Ui/NewsWriteBoundUserInterface.cs +++ b/Content.Client/MassMedia/Ui/NewsWriteBoundUserInterface.cs @@ -1,9 +1,6 @@ -using Robust.Shared.Timing; using JetBrains.Annotations; -using Robust.Client.GameObjects; -using Content.Shared.MassMedia.Systems; using Content.Shared.MassMedia.Components; -using Content.Client.GameTicking.Managers; +using Content.Shared.MassMedia.Systems; using Robust.Shared.Utility; namespace Content.Client.MassMedia.Ui @@ -14,16 +11,11 @@ public sealed class NewsWriteBoundUserInterface : BoundUserInterface [ViewVariables] private NewsWriteMenu? _menu; - [Dependency] private readonly IEntitySystemManager _entitySystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - private ClientGameTicker? _gameTicker; - [ViewVariables] private string _windowName = Loc.GetString("news-read-ui-default-title"); public NewsWriteBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { - } protected override void Open() @@ -36,8 +28,6 @@ protected override void Open() _menu.ShareButtonPressed += OnShareButtonPressed; _menu.DeleteButtonPressed += OnDeleteButtonPressed; - _gameTicker = _entitySystem.GetEntitySystem(); - SendMessage(new NewsWriteArticlesRequestMessage()); } @@ -67,19 +57,21 @@ private void OnShareButtonPressed() var stringContent = Rope.Collapse(_menu.ContentInput.TextRope); - if (stringContent == null || stringContent.Length == 0) + if (stringContent.Length == 0) return; - var stringName = _menu.NameInput.Text; - var name = (stringName.Length <= 25 ? stringName.Trim() : $"{stringName.Trim().Substring(0, 25)}..."); + var stringName = _menu.NameInput.Text.Trim(); + var name = stringName[..Math.Min(stringName.Length, (SharedNewsSystem.MaxNameLength))]; + var content = stringContent[..Math.Min(stringContent.Length, (SharedNewsSystem.MaxArticleLength))]; _menu.ContentInput.TextRope = new Rope.Leaf(string.Empty); _menu.NameInput.Text = string.Empty; - SendMessage(new NewsWriteShareMessage(name, stringContent)); + SendMessage(new NewsWriteShareMessage(name, content)); } private void OnDeleteButtonPressed(int articleNum) { - if (_menu == null) return; + if (_menu == null) + return; SendMessage(new NewsWriteDeleteMessage(articleNum)); } diff --git a/Content.Client/Materials/UI/MaterialDisplay.xaml b/Content.Client/Materials/UI/MaterialDisplay.xaml new file mode 100644 index 0000000000..097736ddd3 --- /dev/null +++ b/Content.Client/Materials/UI/MaterialDisplay.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/Content.Client/Materials/UI/MaterialDisplay.xaml.cs b/Content.Client/Materials/UI/MaterialDisplay.xaml.cs new file mode 100644 index 0000000000..eaa16af658 --- /dev/null +++ b/Content.Client/Materials/UI/MaterialDisplay.xaml.cs @@ -0,0 +1,110 @@ +using Content.Client.Stylesheets; +using Content.Shared.Materials; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client.Materials.UI; + +/// +/// This widget is one row in the material storage control. +/// +[GenerateTypedNameReferences] +public sealed partial class MaterialDisplay : PanelContainer +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + private readonly MaterialStorageSystem _materialStorage; + + private readonly MaterialStorageUIController _materialUIController; + + private int _volume; + private readonly EntityUid _ent; + public readonly string Material; + private readonly bool _canEject; + + public MaterialDisplay(EntityUid ent, string material, int volume, bool canEject) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + _materialStorage = _entityManager.System(); + _materialUIController = UserInterfaceManager.GetUIController(); + + var spriteSys = _entityManager.System(); + Icon.Texture = spriteSys.Frame0(_prototypeManager.Index(material).Icon); + + _ent = ent; + Material = material; + _canEject = canEject; + UpdateVolume(volume); + } + + public void UpdateVolume(int volume) + { + if (_volume == volume) + return; + + _volume = volume; + var matProto = _prototypeManager.Index(Material); + + var sheetVolume = _materialStorage.GetSheetVolume(matProto); + var sheets = (float) volume / sheetVolume; + var maxEjectableSheets = (int) MathF.Floor(sheets); + + var unit = Loc.GetString(matProto.Unit); + var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit)); + var name = Loc.GetString(matProto.Name); + var mat = Loc.GetString("lathe-menu-material-display", ("material", name), ("amount", amountText)); + ProductName.Text = mat; + + PopulateButtons(maxEjectableSheets); + } + + public void PopulateButtons(int maxEjectableSheets) + { + Content.Children.Clear(); + if (!_canEject) + return; + + int[] sheetsToEjectArray = { 1, 5, 10 }; + + for (var i = 0; i < sheetsToEjectArray.Length; i++) + { + var sheetsToEject = sheetsToEjectArray[i]; + + var styleClass = StyleBase.ButtonOpenBoth; + if (i == 0) + styleClass = StyleBase.ButtonOpenRight; + else if (i == sheetsToEjectArray.Length - 1) + styleClass = StyleBase.ButtonOpenLeft; + + var button = new Button + { + Name = $"{sheetsToEject}", + Access = AccessLevel.Public, + Text = Loc.GetString($"{sheetsToEject}"), + MinWidth = 45, + StyleClasses = { styleClass } + }; + + button.OnPressed += _ => + { + _materialUIController.SendLatheEjectMessage(_ent, Material, sheetsToEject); + }; + + button.Disabled = maxEjectableSheets < sheetsToEject; + + if (_prototypeManager.TryIndex(Material, out var proto)) + { + button.ToolTip = Loc.GetString("lathe-menu-tooltip-display", ("amount", sheetsToEject), ("material", Loc.GetString(proto.Name))); + } + + Content.AddChild(button); + } + } +} diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml b/Content.Client/Materials/UI/MaterialStorageControl.xaml new file mode 100644 index 0000000000..e504434649 --- /dev/null +++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml @@ -0,0 +1,7 @@ + + diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs new file mode 100644 index 0000000000..3ef247d529 --- /dev/null +++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs @@ -0,0 +1,92 @@ +using System.Linq; +using Content.Shared.Materials; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Timing; + +namespace Content.Client.Materials.UI; + +/// +/// This widget is one row in the lathe eject menu. +/// +[GenerateTypedNameReferences] +public sealed partial class MaterialStorageControl : BoxContainer +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + + private EntityUid? _owner; + + private Dictionary _currentMaterials = new(); + + public MaterialStorageControl() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + } + + public void SetOwner(EntityUid owner) + { + _owner = owner; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + if (_owner == null) + return; + + if (_entityManager.Deleted(_owner) || !_entityManager.TryGetComponent(_owner, out var materialStorage)) + { + _owner = null; + return; + } + + var canEject = materialStorage.CanEjectStoredMaterials; + var mats = materialStorage.Storage.Select(pair => (pair.Key.Id, pair.Value)).ToDictionary(); + if (_currentMaterials.Equals(mats)) + return; + + var missing = new List(); + var extra = new List(); + foreach (var (mat, amount) in mats) + { + if (!_currentMaterials.ContainsKey(mat) || + _currentMaterials[mat] == 0 && _currentMaterials[mat] != amount) + missing.Add(mat); + } + foreach (var (mat, amount) in _currentMaterials) + { + if (!mats.ContainsKey(mat) || amount == 0) + extra.Add(mat); + } + + var children = new List(); + children.AddRange(Children.OfType()); + + foreach (var display in children) + { + var mat = display.Material; + + if (extra.Contains(mat)) + { + RemoveChild(display); + continue; + } + + if (!mats.TryGetValue(mat, out var newAmount)) + continue; + display.UpdateVolume(newAmount); + } + + foreach (var mat in missing) + { + var volume = mats[mat]; + AddChild(new MaterialDisplay(_owner.Value, mat, volume, canEject)); + } + + _currentMaterials = mats; + NoMatsLabel.Visible = ChildCount == 1; + } +} diff --git a/Content.Client/Materials/UI/MaterialStorageUIController.cs b/Content.Client/Materials/UI/MaterialStorageUIController.cs new file mode 100644 index 0000000000..943aa0a15a --- /dev/null +++ b/Content.Client/Materials/UI/MaterialStorageUIController.cs @@ -0,0 +1,12 @@ +using Content.Shared.Materials; +using Robust.Client.UserInterface.Controllers; + +namespace Content.Client.Materials.UI; + +public sealed class MaterialStorageUIController : UIController +{ + public void SendLatheEjectMessage(EntityUid uid, string material, int sheetsToEject) + { + EntityManager.RaisePredictiveEvent(new EjectMaterialMessage(EntityManager.GetNetEntity(uid), material, sheetsToEject)); + } +} diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs index e6adf13bed..fcecbad465 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs @@ -9,7 +9,6 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl public NetEntity? Focus; public Dictionary LocalizedNames = new(); - private Color _backgroundColor; private Label _trackedEntityLabel; private PanelContainer _trackedEntityPanel; @@ -17,8 +16,7 @@ public CrewMonitoringNavMapControl() : base() { WallColor = new Color(192, 122, 196); TileColor = new(71, 42, 72); - - _backgroundColor = Color.FromSrgb(TileColor.WithAlpha(0.8f)); + _backgroundColor = Color.FromSrgb(TileColor.WithAlpha(_backgroundOpacity)); _trackedEntityLabel = new Label { diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml index 80bf5a3f8b..660f2e5e11 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml @@ -2,18 +2,19 @@ xmlns:ui="clr-namespace:Content.Client.Medical.CrewMonitoring" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" Title="{Loc 'crew-monitoring-user-interface-title'}" - SetSize="1200 700" - MinSize="1200 700"> + Resizable="False" + SetSize="1210 700" + MinSize="1210 700"> - + - - + +/// Handles setting sprite states based on whether an entity has movement input. +/// +public sealed class SpriteMovementSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + + private EntityQuery _spriteQuery; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnSpriteMoveInput); + _spriteQuery = GetEntityQuery(); + } + + private void OnSpriteMoveInput(EntityUid uid, SpriteMovementComponent component, ref MoveInputEvent args) + { + if (!_timing.IsFirstTimePredicted) + return; + + var oldMoving = (SharedMoverController.GetNormalizedMovement(args.OldMovement) & MoveButtons.AnyDirection) != MoveButtons.None; + var moving = (SharedMoverController.GetNormalizedMovement(args.Component.HeldMoveButtons) & MoveButtons.AnyDirection) != MoveButtons.None; + + if (oldMoving == moving || !_spriteQuery.TryGetComponent(uid, out var sprite)) + return; + + if (moving) + { + foreach (var (layer, state) in component.MovementLayers) + { + sprite.LayerSetData(layer, state); + } + } + else + { + foreach (var (layer, state) in component.NoMovementLayers) + { + sprite.LayerSetData(layer, state); + } + } + } +} diff --git a/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs b/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs index 9047d7cc8a..0cc090fcae 100644 --- a/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs @@ -31,13 +31,13 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(_ => ClearAllOverlays()); - SubscribeLocalEvent(OnCollectItemStatus); + Subs.ItemStatus(OnCollectItemStatus); } - private void OnCollectItemStatus(EntityUid uid, NetworkConfiguratorComponent configurator, ItemStatusCollectMessage args) + private Control OnCollectItemStatus(Entity entity) { _inputManager.TryGetKeyBinding((ContentKeyFunctions.AltUseItemInHand), out var binding); - args.Controls.Add(new StatusControl(configurator, binding?.GetKeyString() ?? "")); + return new StatusControl(entity, binding?.GetKeyString() ?? ""); } public bool ConfiguredListIsTracked(EntityUid uid, NetworkConfiguratorComponent? component = null) diff --git a/Content.Client/NukeOps/WarDeclaratorBoundUserInterface.cs b/Content.Client/NukeOps/WarDeclaratorBoundUserInterface.cs index 7394e27043..20103a9743 100644 --- a/Content.Client/NukeOps/WarDeclaratorBoundUserInterface.cs +++ b/Content.Client/NukeOps/WarDeclaratorBoundUserInterface.cs @@ -1,13 +1,16 @@ -using Content.Shared.NukeOps; +using Content.Shared.CCVar; +using Content.Shared.Chat; +using Content.Shared.NukeOps; using JetBrains.Annotations; -using Robust.Client.GameObjects; -using Robust.Shared.Timing; +using Robust.Shared.Configuration; namespace Content.Client.NukeOps; [UsedImplicitly] public sealed class WarDeclaratorBoundUserInterface : BoundUserInterface { + [Dependency] private readonly IConfigurationManager _cfg = default!; + [ViewVariables] private WarDeclaratorWindow? _window; @@ -44,6 +47,8 @@ protected override void Dispose(bool disposing) private void OnWarDeclaratorActivated(string message) { - SendMessage(new WarDeclaratorActivateMessage(message)); + var maxLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength); + var msg = SharedChatSystem.SanitizeAnnouncement(message, maxLength); + SendMessage(new WarDeclaratorActivateMessage(msg)); } } diff --git a/Content.Client/NukeOps/WarDeclaratorWindow.xaml.cs b/Content.Client/NukeOps/WarDeclaratorWindow.xaml.cs index 8fb10b8215..104e776daa 100644 --- a/Content.Client/NukeOps/WarDeclaratorWindow.xaml.cs +++ b/Content.Client/NukeOps/WarDeclaratorWindow.xaml.cs @@ -2,7 +2,6 @@ using Content.Shared.NukeOps; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; -using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Timing; @@ -27,7 +26,7 @@ public WarDeclaratorWindow() _gameTiming = IoCManager.Resolve(); - WarButton.OnPressed += ActivateWarDeclarator; + WarButton.OnPressed += (_) => OnActivated?.Invoke(Rope.Collapse(MessageEdit.TextRope)); var loc = IoCManager.Resolve(); MessageEdit.Placeholder = new Rope.Leaf(loc.GetString("war-declarator-message-placeholder")); @@ -129,10 +128,4 @@ public void UpdateTimer() return; } } - - private void ActivateWarDeclarator(BaseButton.ButtonEventArgs obj) - { - var message = Rope.Collapse(MessageEdit.TextRope); - OnActivated?.Invoke(message); - } } diff --git a/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs b/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs index a1252cf43e..689dcc00fc 100644 --- a/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs +++ b/Content.Client/Nyanotrasen/Kitchen/Components/DeepFriedComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Kitchen.Components; +using Content.Shared.Nyanotrasen.Kitchen.Components; namespace Content.Client.Kitchen.Components { diff --git a/Content.Client/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs b/Content.Client/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs index 4123eb341c..34d5a864d4 100644 --- a/Content.Client/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs +++ b/Content.Client/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Kitchen.Components; +using Content.Shared.Nyanotrasen.Kitchen.Components; namespace Content.Client.Kitchen.Components { diff --git a/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerBoundUserInterface.cs b/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerBoundUserInterface.cs index 8db884328e..1d4427c815 100644 --- a/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerBoundUserInterface.cs +++ b/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerBoundUserInterface.cs @@ -1,5 +1,5 @@ +using Content.Shared.Nyanotrasen.Kitchen.UI; using Robust.Client.GameObjects; -using Content.Shared.Kitchen.UI; namespace Content.Client.Nyanotrasen.Kitchen.UI { diff --git a/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerWindow.xaml.cs b/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerWindow.xaml.cs index 7eb8eabd2c..4f9e274eb0 100644 --- a/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerWindow.xaml.cs +++ b/Content.Client/Nyanotrasen/Kitchen/UI/DeepFryerWindow.xaml.cs @@ -1,9 +1,9 @@ +using Content.Shared.Nyanotrasen.Kitchen.UI; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; -using Content.Shared.Kitchen.UI; namespace Content.Client.Nyanotrasen.Kitchen.UI { diff --git a/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs b/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs index 58580e3f5a..97fea3d0ca 100644 --- a/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs +++ b/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFriedVisualizer.cs @@ -5,6 +5,7 @@ using Content.Shared.Clothing; using Content.Shared.Hands; using Content.Shared.Kitchen.Components; +using Content.Shared.Nyanotrasen.Kitchen.Components; namespace Content.Client.Kitchen.Visualizers { diff --git a/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFryerVisualizer.cs b/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFryerVisualizer.cs index 69f613fc11..5d208d0959 100644 --- a/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFryerVisualizer.cs +++ b/Content.Client/Nyanotrasen/Kitchen/Visualizers/DeepFryerVisualizer.cs @@ -1,7 +1,9 @@ using Robust.Client.GameObjects; using Content.Client.Chemistry.Visualizers; using Content.Client.Kitchen.Components; +using Content.Shared.Chemistry.Components; using Content.Shared.Kitchen.Components; +using Content.Shared.Nyanotrasen.Kitchen.Components; namespace Content.Client.Kitchen.Visualizers { diff --git a/Content.Client/Options/UI/Tabs/AudioTab.xaml b/Content.Client/Options/UI/Tabs/AudioTab.xaml index 528b0ac136..e54b0dc34e 100644 --- a/Content.Client/Options/UI/Tabs/AudioTab.xaml +++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml @@ -74,6 +74,19 @@ + + - - @@ -38,11 +33,6 @@ Rounded="True" MinWidth="200" /> - - diff --git a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs index 1773b2abe5..3113e644ba 100644 --- a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs @@ -1,6 +1,4 @@ -using Content.Client.UserInterface.Screens; using Content.Shared.CCVar; -using Content.Shared.HUD; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface; @@ -8,7 +6,6 @@ using Robust.Client.UserInterface.XAML; using Robust.Shared; using Robust.Shared.Configuration; -using Robust.Shared.Prototypes; namespace Content.Client.Options.UI.Tabs { @@ -26,10 +23,7 @@ public sealed partial class GraphicsTab : Control 2f }; - private Dictionary hudThemeIdToIndex = new(); - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public GraphicsTab() { @@ -55,34 +49,6 @@ public GraphicsTab() UIScaleOption.AddItem(Loc.GetString("ui-options-scale-200")); UIScaleOption.OnItemSelected += OnUIScaleChanged; - foreach (var gear in _prototypeManager.EnumeratePrototypes()) - { - HudThemeOption.AddItem(Loc.GetString(gear.Name)); - hudThemeIdToIndex.Add(gear.ID, HudThemeOption.GetItemId(HudThemeOption.ItemCount - 1)); - } - HudThemeOption.OnItemSelected += OnHudThemeChanged; - - var hudLayout = _cfg.GetCVar(CCVars.UILayout); - var id = 0; - foreach (var layout in Enum.GetValues(typeof(ScreenType))) - { - var name = layout.ToString()!; - HudLayoutOption.AddItem(name, id); - if (name == hudLayout) - { - HudLayoutOption.SelectId(id); - } - HudLayoutOption.SetItemMetadata(id, name); - - id++; - } - - HudLayoutOption.OnItemSelected += args => - { - HudLayoutOption.SelectId(args.Id); - UpdateApplyButton(); - }; - ViewportStretchCheckBox.OnToggled += _ => { UpdateViewportScale(); @@ -110,7 +76,6 @@ public GraphicsTab() FullscreenCheckBox.Pressed = ConfigIsFullscreen; LightingPresetOption.SelectId(GetConfigLightingQuality()); UIScaleOption.SelectId(GetConfigUIScalePreset(ConfigUIScale)); - HudThemeOption.SelectId(hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0)); ViewportScaleSlider.Value = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor); ViewportStretchCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportStretch); IntegerScalingCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0; @@ -134,25 +99,11 @@ private void OnUIScaleChanged(OptionButton.ItemSelectedEventArgs args) UpdateApplyButton(); } - private void OnHudThemeChanged(OptionButton.ItemSelectedEventArgs args) - { - HudThemeOption.SelectId(args.Id); - UpdateApplyButton(); - } - private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args) { _cfg.SetCVar(CVars.DisplayVSync, VSyncCheckBox.Pressed); SetConfigLightingQuality(LightingPresetOption.SelectedId); - foreach (var theme in _prototypeManager.EnumeratePrototypes()) - { - if (hudThemeIdToIndex[theme.ID] != HudThemeOption.SelectedId) - continue; - _cfg.SetCVar(CVars.InterfaceTheme, theme.ID); - break; - } - _cfg.SetCVar(CVars.DisplayWindowMode, (int) (FullscreenCheckBox.Pressed ? WindowMode.Fullscreen : WindowMode.Windowed)); _cfg.SetCVar(CVars.DisplayUIScale, UIScaleOptions[UIScaleOption.SelectedId]); @@ -165,11 +116,6 @@ private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args) _cfg.SetCVar(CCVars.HudFpsCounterVisible, FpsCounterCheckBox.Pressed); _cfg.SetCVar(CCVars.ViewportWidth, (int) ViewportWidthSlider.Value); - if (HudLayoutOption.SelectedMetadata is string opt) - { - _cfg.SetCVar(CCVars.UILayout, opt); - } - _cfg.SaveToFile(); UpdateApplyButton(); } @@ -190,7 +136,6 @@ private void UpdateApplyButton() var isVSyncSame = VSyncCheckBox.Pressed == _cfg.GetCVar(CVars.DisplayVSync); var isFullscreenSame = FullscreenCheckBox.Pressed == ConfigIsFullscreen; var isLightingQualitySame = LightingPresetOption.SelectedId == GetConfigLightingQuality(); - var isHudThemeSame = HudThemeOption.SelectedId == hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0); var isUIScaleSame = MathHelper.CloseToPercent(UIScaleOptions[UIScaleOption.SelectedId], ConfigUIScale); var isVPStretchSame = ViewportStretchCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportStretch); var isVPScaleSame = (int) ViewportScaleSlider.Value == _cfg.GetCVar(CCVars.ViewportFixedScaleFactor); @@ -199,7 +144,6 @@ private void UpdateApplyButton() var isPLQSame = ParallaxLowQualityCheckBox.Pressed == _cfg.GetCVar(CCVars.ParallaxLowQuality); var isFpsCounterVisibleSame = FpsCounterCheckBox.Pressed == _cfg.GetCVar(CCVars.HudFpsCounterVisible); var isWidthSame = (int) ViewportWidthSlider.Value == _cfg.GetCVar(CCVars.ViewportWidth); - var isLayoutSame = HudLayoutOption.SelectedMetadata is string opt && opt == _cfg.GetCVar(CCVars.UILayout); ApplyButton.Disabled = isVSyncSame && isFullscreenSame && @@ -210,10 +154,8 @@ private void UpdateApplyButton() isIntegerScalingSame && isVPResSame && isPLQSame && - isHudThemeSame && isFpsCounterVisibleSame && - isWidthSame && - isLayoutSame; + isWidthSame; } private bool ConfigIsFullscreen => diff --git a/Content.Client/Options/UI/Tabs/MiscTab.xaml b/Content.Client/Options/UI/Tabs/MiscTab.xaml index 8097578d8e..e37333b0e4 100644 --- a/Content.Client/Options/UI/Tabs/MiscTab.xaml +++ b/Content.Client/Options/UI/Tabs/MiscTab.xaml @@ -5,6 +5,23 @@ xmlns:s="clr-namespace:Content.Client.Stylesheets"> + - - - - - - - - - - + + + + + + + + + + + + + + + + + + ? OnProgramItemPressed; @@ -41,6 +51,7 @@ public PdaMenu() FlashLightToggleButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/light.png")); EjectPenButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/pencil.png")); EjectIdButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/eject.png")); + EjectPaiButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/pai.png")); ProgramCloseButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/Nano/cross.svg.png")); @@ -83,6 +94,39 @@ public PdaMenu() ToHomeScreen(); }; + PdaOwnerButton.OnPressed += _ => + { + _clipboard.SetText(_pdaOwner); + }; + + IdInfoButton.OnPressed += _ => + { + _clipboard.SetText(_owner + ", " + _jobTitle); + }; + + StationNameButton.OnPressed += _ => + { + _clipboard.SetText(_stationName); + }; + + StationAlertLevelButton.OnPressed += _ => + { + _clipboard.SetText(_alertLevel); + }; + + StationTimeButton.OnPressed += _ => + { + var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan); + _clipboard.SetText((stationTime.ToString("hh\\:mm\\:ss"))); + }; + + StationAlertLevelInstructionsButton.OnPressed += _ => + { + _clipboard.SetText(_instructions); + }; + + + HideAllViews(); ToHomeScreen(); @@ -94,24 +138,29 @@ public void UpdateState(PdaUpdateState state) if (state.PdaOwnerInfo.ActualOwnerName != null) { + _pdaOwner = state.PdaOwnerInfo.ActualOwnerName; PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner", - ("actualOwnerName", state.PdaOwnerInfo.ActualOwnerName))); + ("actualOwnerName", _pdaOwner))); } if (state.PdaOwnerInfo.IdOwner != null || state.PdaOwnerInfo.JobTitle != null) { + _owner = state.PdaOwnerInfo.IdOwner ?? Loc.GetString("comp-pda-ui-unknown"); + _jobTitle = state.PdaOwnerInfo.JobTitle ?? Loc.GetString("comp-pda-ui-unassigned"); IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui", - ("owner", state.PdaOwnerInfo.IdOwner ?? Loc.GetString("comp-pda-ui-unknown")), - ("jobTitle", state.PdaOwnerInfo.JobTitle ?? Loc.GetString("comp-pda-ui-unassigned")))); + ("owner", _owner), + ("jobTitle", _jobTitle))); } else { IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui-blank")); } + _stationName = state.StationName ?? Loc.GetString("comp-pda-ui-unknown"); StationNameLabel.SetMarkup(Loc.GetString("comp-pda-ui-station", - ("station", state.StationName ?? Loc.GetString("comp-pda-ui-unknown")))); + ("station", _stationName))); + var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan); @@ -121,22 +170,24 @@ public void UpdateState(PdaUpdateState state) var alertLevel = state.PdaOwnerInfo.StationAlertLevel; var alertColor = state.PdaOwnerInfo.StationAlertColor; var alertLevelKey = alertLevel != null ? $"alert-level-{alertLevel}" : "alert-level-unknown"; + _alertLevel = Loc.GetString(alertLevelKey); StationAlertLevelLabel.SetMarkup(Loc.GetString( "comp-pda-ui-station-alert-level", ("color", alertColor), - ("level", Loc.GetString(alertLevelKey)) + ("level", _alertLevel) )); - + _instructions = Loc.GetString($"{alertLevelKey}-instructions"); StationAlertLevelInstructions.SetMarkup(Loc.GetString( "comp-pda-ui-station-alert-level-instructions", - ("instructions", Loc.GetString($"{alertLevelKey}-instructions"))) + ("instructions", _instructions)) ); AddressLabel.Text = state.Address?.ToUpper() ?? " - "; EjectIdButton.IsActive = state.PdaOwnerInfo.IdOwner != null || state.PdaOwnerInfo.JobTitle != null; EjectPenButton.IsActive = state.HasPen; + EjectPaiButton.IsActive = state.HasPai; ActivateMusicButton.Visible = state.CanPlayMusic; ShowUplinkButton.Visible = state.HasUplink; LockUplinkButton.Visible = state.HasUplink; diff --git a/Content.Client/Paper/UI/PaperBoundUserInterface.cs b/Content.Client/Paper/UI/PaperBoundUserInterface.cs index e047fdf84b..30f1502779 100644 --- a/Content.Client/Paper/UI/PaperBoundUserInterface.cs +++ b/Content.Client/Paper/UI/PaperBoundUserInterface.cs @@ -1,5 +1,4 @@ using JetBrains.Annotations; -using Robust.Client.GameObjects; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; using Robust.Shared.Utility; @@ -49,15 +48,12 @@ protected override void UpdateState(BoundUserInterfaceState state) private void Input_OnTextEntered(string text) { - if (!string.IsNullOrEmpty(text)) - { - SendMessage(new PaperInputTextMessage(text)); + SendMessage(new PaperInputTextMessage(text)); - if (_window != null) - { - _window.Input.TextRope = Rope.Leaf.Empty; - _window.Input.CursorPosition = new TextEdit.CursorPos(0, TextEdit.LineBreakBias.Top); - } + if (_window != null) + { + _window.Input.TextRope = Rope.Leaf.Empty; + _window.Input.CursorPosition = new TextEdit.CursorPos(0, TextEdit.LineBreakBias.Top); } } diff --git a/Content.Client/Pinpointer/NavMapSystem.cs b/Content.Client/Pinpointer/NavMapSystem.cs index d346d90904..bd7dfc1117 100644 --- a/Content.Client/Pinpointer/NavMapSystem.cs +++ b/Content.Client/Pinpointer/NavMapSystem.cs @@ -33,6 +33,9 @@ private void OnHandleState(EntityUid uid, NavMapComponent component, ref Compone component.Beacons.Clear(); component.Beacons.AddRange(state.Beacons); + + component.Airlocks.Clear(); + component.Airlocks.AddRange(state.Airlocks); } } diff --git a/Content.Client/Pinpointer/UI/NavMapBeaconBoundUserInterface.cs b/Content.Client/Pinpointer/UI/NavMapBeaconBoundUserInterface.cs new file mode 100644 index 0000000000..3ebcf7cbce --- /dev/null +++ b/Content.Client/Pinpointer/UI/NavMapBeaconBoundUserInterface.cs @@ -0,0 +1,34 @@ +using Content.Shared.Pinpointer; +using JetBrains.Annotations; + +namespace Content.Client.Pinpointer.UI; + +[UsedImplicitly] +public sealed class NavMapBeaconBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private NavMapBeaconWindow? _window; + + public NavMapBeaconBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + _window = new NavMapBeaconWindow(Owner); + _window.OpenCentered(); + _window.OnClose += Close; + + _window.OnApplyButtonPressed += (label, enabled, color) => + { + SendMessage(new NavMapBeaconConfigureBuiMessage(label, enabled, color)); + }; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _window?.Dispose(); + } +} diff --git a/Content.Client/Pinpointer/UI/NavMapBeaconWindow.xaml b/Content.Client/Pinpointer/UI/NavMapBeaconWindow.xaml new file mode 100644 index 0000000000..88c3506263 --- /dev/null +++ b/Content.Client/Pinpointer/UI/NavMapBeaconWindow.xaml @@ -0,0 +1,17 @@ + + + +