diff --git a/Content.Benchmarks/DynamicTreeBenchmark.cs b/Content.Benchmarks/DynamicTreeBenchmark.cs index 4f3dad4073..9086b4d3ac 100644 --- a/Content.Benchmarks/DynamicTreeBenchmark.cs +++ b/Content.Benchmarks/DynamicTreeBenchmark.cs @@ -44,7 +44,7 @@ namespace Content.Benchmarks for (var i = 0; i < Aabbs1.Length; i++) { var aabb = Aabbs1[i]; - _b2Tree.CreateProxy(aabb, i); + _b2Tree.CreateProxy(aabb, uint.MaxValue, i); _tree.Add(i); } } diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs index 3e05018c10..973f1a090b 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs +++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs @@ -36,6 +36,9 @@ namespace Content.Client.Administration.UI.Bwoink RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); + var newPlayerThreshold = 0; + _cfg.OnValueChanged(CCVars.NewPlayerThreshold, (val) => { newPlayerThreshold = val; }, true); + var uiController = _ui.GetUIController(); if (uiController.UIHelper is not AdminAHelpUIHandler helper) return; @@ -59,9 +62,9 @@ namespace Content.Client.Administration.UI.Bwoink var sb = new StringBuilder(); if (info.Connected) - sb.Append('●'); + sb.Append(info.ActiveThisRound ? '⚫' : '◐'); else - sb.Append(info.ActiveThisRound ? '○' : '·'); + sb.Append(info.ActiveThisRound ? '⭘' : '·'); sb.Append(' '); if (AHelpHelper.TryGetChannel(info.SessionId, out var panel) && panel.Unread > 0) @@ -73,10 +76,12 @@ namespace Content.Client.Administration.UI.Bwoink sb.Append(' '); } + // Mark antagonists with symbol if (info.Antag && info.ActiveThisRound) sb.Append(new Rune(0x1F5E1)); // 🗡 - if (info.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold))) + // Mark new players with symbol + if (IsNewPlayer(info)) sb.Append(new Rune(0x23F2)); // ⏲ sb.AppendFormat("\"{0}\"", text); @@ -84,6 +89,19 @@ namespace Content.Client.Administration.UI.Bwoink return sb.ToString(); }; + // + // Returns true if the player's overall playtime is under the set threshold + // + bool IsNewPlayer(PlayerInfo info) + { + // Don't show every disconnected player as new, don't show 0-minute players as new if threshold is + if (newPlayerThreshold <= 0 || info.OverallPlaytime is null && !info.Connected) + return false; + + return (info.OverallPlaytime is null + || info.OverallPlaytime < TimeSpan.FromMinutes(newPlayerThreshold)); + } + ChannelSelector.Comparison = (a, b) => { var ach = AHelpHelper.EnsurePanel(a.SessionId); @@ -93,31 +111,37 @@ namespace Content.Client.Administration.UI.Bwoink if (a.IsPinned != b.IsPinned) return a.IsPinned ? -1 : 1; - // First, sort by unread. Any chat with unread messages appears first. + // Then, any chat with unread messages. var aUnread = ach.Unread > 0; var bUnread = bch.Unread > 0; if (aUnread != bUnread) return aUnread ? -1 : 1; - // Sort by recent messages during the current round. + // Then, any chat with recent messages from the current round var aRecent = a.ActiveThisRound && ach.LastMessage != DateTime.MinValue; var bRecent = b.ActiveThisRound && bch.LastMessage != DateTime.MinValue; if (aRecent != bRecent) return aRecent ? -1 : 1; - // Next, sort by connection status. Any disconnected players are grouped towards the end. + // Sort by connection status. Disconnected players will be last. if (a.Connected != b.Connected) return a.Connected ? -1 : 1; - // Sort connected players by New Player status, then by Antag status + // Sort connected players by whether they have joined the round, then by New Player status, then by Antag status if (a.Connected && b.Connected) { - var aNewPlayer = a.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)); - var bNewPlayer = b.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)); + var aNewPlayer = IsNewPlayer(a); + var bNewPlayer = IsNewPlayer(b); + // Players who have joined the round will be listed before players in the lobby + if (a.ActiveThisRound != b.ActiveThisRound) + return a.ActiveThisRound ? -1 : 1; + + // Within both the joined group and lobby group, new players will be grouped and listed first if (aNewPlayer != bNewPlayer) return aNewPlayer ? -1 : 1; + // Within all four previous groups, antagonists will be listed first. if (a.Antag != b.Antag) return a.Antag ? -1 : 1; } diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs index e8653843c7..e6cd4942a6 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs +++ b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs @@ -22,12 +22,9 @@ namespace Content.Client.Administration.UI.Bwoink return; } - Title = $"{sel.CharacterName} / {sel.Username}"; + Title = $"{sel.CharacterName} / {sel.Username} | {Loc.GetString("generic-playtime-title")}: "; - if (sel.OverallPlaytime != null) - { - Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}"; - } + Title += sel.OverallPlaytime != null ? sel.PlaytimeString : Loc.GetString("generic-unknown-title"); }; OnOpen += () => diff --git a/Content.Client/Advertise/Systems/SpeakOnUIClosedSystem.cs b/Content.Client/Advertise/Systems/SpeakOnUIClosedSystem.cs new file mode 100644 index 0000000000..4e82ec4d00 --- /dev/null +++ b/Content.Client/Advertise/Systems/SpeakOnUIClosedSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Advertise.Systems; + +namespace Content.Client.Advertise.Systems; + +public sealed class SpeakOnUIClosedSystem : SharedSpeakOnUIClosedSystem; diff --git a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs index 010bfb3184..0ba537e455 100644 --- a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs +++ b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs @@ -28,7 +28,6 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem - - - - - - - - - + + + + + + + + + + + + diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs index d735e264ce..1bffc1e5b2 100644 --- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -1,8 +1,10 @@ +using Content.Shared.CCVar; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; using Robust.Client.GameObjects; +using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using System.Numerics; // CD - Character Records @@ -14,12 +16,15 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly MarkingManager _markingManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnHandleState); + Subs.CVar(_configurationManager, CCVars.AccessibilityClientCensorNudity, OnCvarChanged, true); + Subs.CVar(_configurationManager, CCVars.AccessibilityServerCensorNudity, OnCvarChanged, true); } private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref AfterAutoHandleStateEvent args) @@ -27,6 +32,15 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem UpdateSprite(component, Comp(uid)); } + private void OnCvarChanged(bool value) + { + var humanoidQuery = EntityManager.AllEntityQueryEnumerator(); + while (humanoidQuery.MoveNext(out var _, out var humanoidComp, out var spriteComp)) + { + UpdateSprite(humanoidComp, spriteComp); + } + } + private void UpdateSprite(HumanoidAppearanceComponent component, SpriteComponent sprite) { UpdateLayers(component, sprite); @@ -219,16 +233,30 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem // Really, markings should probably be a separate component altogether. ClearAllMarkings(humanoid, sprite); + var censorNudity = _configurationManager.GetCVar(CCVars.AccessibilityClientCensorNudity) || + _configurationManager.GetCVar(CCVars.AccessibilityServerCensorNudity); + // The reason we're splitting this up is in case the character already has undergarment equipped in that slot. + var applyUndergarmentTop = censorNudity; + var applyUndergarmentBottom = censorNudity; + foreach (var markingList in humanoid.MarkingSet.Markings.Values) { foreach (var marking in markingList) { if (_markingManager.TryGetMarking(marking, out var markingPrototype)) + { ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite); + if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentTop) + applyUndergarmentTop = false; + else if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentBottom) + applyUndergarmentBottom = false; + } } } humanoid.ClientOldMarkings = new MarkingSet(humanoid.MarkingSet); + + AddUndergarments(humanoid, sprite, applyUndergarmentTop, applyUndergarmentBottom); } private void ClearAllMarkings(HumanoidAppearanceComponent humanoid, SpriteComponent sprite) @@ -276,6 +304,31 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem spriteComp.RemoveLayer(index); } } + + private void AddUndergarments(HumanoidAppearanceComponent humanoid, SpriteComponent sprite, bool undergarmentTop, bool undergarmentBottom) + { + if (undergarmentTop && humanoid.UndergarmentTop != null) + { + var marking = new Marking(humanoid.UndergarmentTop, new List { new Color() }); + if (_markingManager.TryGetMarking(marking, out var prototype)) + { + // Markings are added to ClientOldMarkings because otherwise it causes issues when toggling the feature on/off. + humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentTop, new List{ marking }); + ApplyMarking(prototype, null, true, humanoid, sprite); + } + } + + if (undergarmentBottom && humanoid.UndergarmentBottom != null) + { + var marking = new Marking(humanoid.UndergarmentBottom, new List { new Color() }); + if (_markingManager.TryGetMarking(marking, out var prototype)) + { + humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentBottom, new List{ marking }); + ApplyMarking(prototype, null, true, humanoid, sprite); + } + } + } + private void ApplyMarking(MarkingPrototype markingPrototype, IReadOnlyList? colors, bool visible, diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml index d84449ce43..f541c8ebd2 100644 --- a/Content.Client/Lathe/UI/LatheMenu.xaml +++ b/Content.Client/Lathe/UI/LatheMenu.xaml @@ -59,6 +59,7 @@ PlaceHolder="0" Text="1" HorizontalExpand="True" /> +