diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 21c3070b802..da9d4d693a8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,7 @@ /Content.*/GameTicking/ @moonheart08 @EmoGarbage404 /Resources/ServerInfo/ @moonheart08 @Chief-Engineer /Resources/ServerInfo/Guidebook/ @moonheart08 @EmoGarbage404 +/Resources/ServerInfo/Guidebook/ServerRules/ @Chief-Engineer /Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer /Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer @@ -23,6 +24,7 @@ /Resources/Prototypes/Body/ @DrSmugleaf # suffering /Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf /Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf +/Resources/Prototypes/Guidebook/rules.yml @Chief-Engineer /Content.*/Body/ @DrSmugleaf /Content.YAMLLinter @DrSmugleaf /Content.Shared/Damage/ @DrSmugleaf diff --git a/.github/workflows/validate-rgas.yml b/.github/workflows/validate-rgas.yml index 2c4bb40fdf3..ffb643feea8 100644 --- a/.github/workflows/validate-rgas.yml +++ b/.github/workflows/validate-rgas.yml @@ -13,13 +13,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3.6.0 - - name: Setup Submodule - run: git submodule update --init - - name: Pull engine updates - uses: space-wizards/submodule-dependency@v0.1.5 - uses: PaulRitter/yaml-schema-validator@v1 with: - schema: RobustToolbox/Schemas/rga.yml + schema: Tools/Schemas/rga.yml path_pattern: .*attributions.ya?ml$ - validators_path: RobustToolbox/Schemas/rga_validators.py - validators_requirements: RobustToolbox/Schemas/rga_requirements.txt + validators_path: Tools/Schemas/rga_validators.py + validators_requirements: Tools/Schemas/rga_requirements.txt diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml index fb68e6c7908..ea89916ba8c 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml @@ -1,15 +1,21 @@  + xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" + xmlns:ot="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab" + xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"> + + - - - - + + + + + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs index a5c30084365..90559707f92 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs @@ -1,5 +1,7 @@ using Content.Client.Station; +using Content.Client.UserInterface.Controls; using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.XAML; using Robust.Shared.Map.Components; @@ -10,20 +12,20 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab; [GenerateTypedNameReferences] public sealed partial class ObjectsTab : Control { - [Dependency] private readonly EntityManager _entityManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IGameTiming _timing = default!; private readonly List _objects = new(); - private List _selections = new(); + private readonly List _selections = new(); + private bool _ascending = false; // Set to false for descending order by default + private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName; + private readonly Color _altColor = Color.FromHex("#292B38"); + private readonly Color _defaultColor = Color.FromHex("#2F2F3B"); - public event Action? OnEntryKeyBindDown; + public event Action? OnEntryKeyBindDown; - // Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController - // OR - // I can do this. - private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2); - - private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2); + private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2); + private TimeSpan _nextUpdate; public ObjectsTab() { @@ -42,6 +44,30 @@ public ObjectsTab() ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!); } + ListHeader.OnHeaderClicked += HeaderClicked; + SearchList.SearchBar = SearchLineEdit; + SearchList.GenerateItem += GenerateButton; + SearchList.DataFilterCondition += DataFilterCondition; + + RefreshObjectList(); + // Set initial selection and refresh the list to apply the initial sort order + var defaultSelection = ObjectsTabSelection.Grids; + ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection + RefreshObjectList(defaultSelection); // Refresh the list with the default selection + + // Initialize the next update time + _nextUpdate = TimeSpan.Zero; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + if (_timing.CurTime < _nextUpdate) + return; + + _nextUpdate = _timing.CurTime + _updateFrequency; + RefreshObjectList(); } @@ -81,32 +107,72 @@ private void RefreshObjectList(ObjectsTabSelection selection) throw new ArgumentOutOfRangeException(nameof(selection), selection, null); } - foreach (var control in _objects) + entities.Sort((a, b) => { - ObjectList.RemoveChild(control); - } + var valueA = GetComparableValue(a, _headerClicked); + var valueB = GetComparableValue(b, _headerClicked); + return _ascending ? Comparer.Default.Compare(valueA, valueB) : Comparer.Default.Compare(valueB, valueA); + }); - _objects.Clear(); - - foreach (var (name, nent) in entities) + var listData = new List(); + for (int index = 0; index < entities.Count; index++) { - var ctrl = new ObjectsTabEntry(name, nent); - _objects.Add(ctrl); - ObjectList.AddChild(ctrl); - ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args); + var info = entities[index]; + listData.Add(new ObjectsListData(info, $"{info.Name} {info.Entity}", index % 2 == 0 ? _altColor : _defaultColor)); } + + SearchList.PopulateList(listData); } - protected override void FrameUpdate(FrameEventArgs args) + private void GenerateButton(ListData data, ListContainerButton button) { - base.FrameUpdate(args); - - if (_timing.CurTime < _nextUpdate) + if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor }) return; - // I do not care for precision. - _nextUpdate = _timing.CurTime + _updateFrequency; + var entry = new ObjectsTabEntry(info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor }); + button.ToolTip = $"{info.Name}, {info.Entity}"; + + // Add key binding event handler + entry.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(args, data); + + button.AddChild(entry); + } + + private bool DataFilterCondition(string filter, ListData listData) + { + if (listData is not ObjectsListData { FilteringString: var filteringString }) + return false; + + // If the filter is empty, do not filter out any entries + if (string.IsNullOrEmpty(filter)) + return true; + + return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase); + } + + private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header) + { + return header switch + { + ObjectsTabHeader.Header.ObjectName => entity.Name, + ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(), + _ => entity.Name + }; + } + + private void HeaderClicked(ObjectsTabHeader.Header header) + { + if (_headerClicked == header) + { + _ascending = !_ascending; + } + else + { + _headerClicked = header; + _ascending = true; + } + ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending); RefreshObjectList(); } @@ -118,3 +184,4 @@ private enum ObjectsTabSelection } } +public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor) : ListData; diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml index 0f6975e3656..83c4cc5697f 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml @@ -1,6 +1,6 @@ - - + @@ -14,4 +14,4 @@ HorizontalExpand="True" ClipText="True"/> - + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs index c9b2cd8b572..aab06c6ccd0 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs @@ -1,19 +1,21 @@ using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; namespace Content.Client.Administration.UI.Tabs.ObjectsTab; [GenerateTypedNameReferences] -public sealed partial class ObjectsTabEntry : ContainerButton +public sealed partial class ObjectsTabEntry : PanelContainer { public NetEntity AssocEntity; - public ObjectsTabEntry(string name, NetEntity nent) + public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox) { RobustXamlLoader.Load(this); AssocEntity = nent; EIDLabel.Text = nent.ToString(); NameLabel.Text = name; + BackgroundColorPanel.PanelOverride = styleBox; } } diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml new file mode 100644 index 00000000000..71a1f5c7bc6 --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml @@ -0,0 +1,21 @@ + + + + + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs new file mode 100644 index 00000000000..3a91b5b9483 --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs @@ -0,0 +1,86 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Input; + +namespace Content.Client.Administration.UI.Tabs.ObjectsTab +{ + [GenerateTypedNameReferences] + public sealed partial class ObjectsTabHeader : Control + { + public event Action
? OnHeaderClicked; + + private const string ArrowUp = "↑"; + private const string ArrowDown = "↓"; + + public ObjectsTabHeader() + { + RobustXamlLoader.Load(this); + + ObjectNameLabel.OnKeyBindDown += ObjectNameClicked; + EntityIDLabel.OnKeyBindDown += EntityIDClicked; + } + + public Label GetHeader(Header header) + { + return header switch + { + Header.ObjectName => ObjectNameLabel, + Header.EntityID => EntityIDLabel, + _ => throw new ArgumentOutOfRangeException(nameof(header), header, null) + }; + } + + public void ResetHeaderText() + { + ObjectNameLabel.Text = Loc.GetString("object-tab-object-name"); + EntityIDLabel.Text = Loc.GetString("object-tab-entity-id"); + } + + public void UpdateHeaderSymbols(Header headerClicked, bool ascending) + { + ResetHeaderText(); + var arrow = ascending ? ArrowUp : ArrowDown; + GetHeader(headerClicked).Text += $" {arrow}"; + } + + private void HeaderClicked(GUIBoundKeyEventArgs args, Header header) + { + if (args.Function != EngineKeyFunctions.UIClick) + { + return; + } + + OnHeaderClicked?.Invoke(header); + args.Handle(); + } + + private void ObjectNameClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.ObjectName); + } + + private void EntityIDClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.EntityID); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked; + EntityIDLabel.OnKeyBindDown -= EntityIDClicked; + } + } + + public enum Header + { + ObjectName, + EntityID + } + } +} diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs index a8bfaddecf4..826945e7cc0 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs @@ -53,6 +53,7 @@ public PlayerTab() SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data); RefreshPlayerList(_adminSystem.PlayerList); + } #region Antag Overlay @@ -110,7 +111,9 @@ private void RefreshPlayerList(IReadOnlyList players) _players = players; PlayerCount.Text = $"Players: {_playerMan.PlayerCount}"; - var sortedPlayers = new List(players); + var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList(); + + var sortedPlayers = new List(filteredPlayers); sortedPlayers.Sort(Compare); UpdateHeaderSymbols(); diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml index eec5c229cb5..322e4e66a9a 100644 --- a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml +++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml @@ -8,7 +8,7 @@ - + diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs index ad19bc30f42..eae70d53679 100644 --- a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs +++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs @@ -23,7 +23,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow private readonly ItemSlotsSystem _itemSlots; private readonly FlatpackSystem _flatpack; private readonly MaterialStorageSystem _materialStorage; - private readonly SpriteSystem _spriteSystem; private readonly EntityUid _owner; @@ -31,7 +30,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow public const string NoBoardEffectId = "FlatpackerNoBoardEffect"; private EntityUid? _currentBoard = EntityUid.Invalid; - private EntityUid? _machinePreview; public event Action? PackButtonPressed; @@ -43,7 +41,6 @@ public FlatpackCreatorMenu(EntityUid uid) _itemSlots = _entityManager.System(); _flatpack = _entityManager.System(); _materialStorage = _entityManager.System(); - _spriteSystem = _entityManager.System(); _owner = uid; @@ -57,17 +54,10 @@ protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); - if (_machinePreview is not { } && _entityManager.Deleted(_machinePreview)) - { - _machinePreview = null; - MachineSprite.SetEntity(_machinePreview); - } - if (!_entityManager.TryGetComponent(_owner, out var flatpacker) || !_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot)) return; - MachineBoardComponent? machineBoardComp = null; if (flatpacker.Packing) { PackButton.Disabled = true; @@ -75,8 +65,7 @@ protected override void FrameUpdate(FrameEventArgs args) else if (_currentBoard != null) { Dictionary cost; - if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp) && - machineBoardComp.Prototype is not null) + if (_entityManager.TryGetComponent(_currentBoard, out var machineBoardComp)) cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp)); else cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker)); @@ -87,9 +76,6 @@ protected override void FrameUpdate(FrameEventArgs args) if (_currentBoard == itemSlot.Item) return; - if (_machinePreview != null) - _entityManager.DeleteEntity(_machinePreview); - _currentBoard = itemSlot.Item; CostHeaderLabel.Visible = _currentBoard != null; InsertLabel.Visible = _currentBoard == null; @@ -99,10 +85,10 @@ protected override void FrameUpdate(FrameEventArgs args) string? prototype = null; Dictionary? cost = null; - if (machineBoardComp != null || _entityManager.TryGetComponent(_currentBoard, out machineBoardComp)) + if (_entityManager.TryGetComponent(_currentBoard, out var newMachineBoardComp)) { - prototype = machineBoardComp.Prototype; - cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp)); + prototype = newMachineBoardComp.Prototype; + cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, newMachineBoardComp)); } else if (_entityManager.TryGetComponent(_currentBoard, out var computerBoard)) { @@ -113,21 +99,18 @@ protected override void FrameUpdate(FrameEventArgs args) if (prototype is not null && cost is not null) { var proto = _prototypeManager.Index(prototype); - _machinePreview = _entityManager.Spawn(proto.ID); - _spriteSystem.ForceUpdate(_machinePreview.Value); + MachineSprite.SetPrototype(prototype); MachineNameLabel.SetMessage(proto.Name); CostLabel.SetMarkup(GetCostString(cost)); } } else { - _machinePreview = _entityManager.Spawn(NoBoardEffectId); + MachineSprite.SetPrototype(NoBoardEffectId); CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label")); MachineNameLabel.SetMessage(" "); PackButton.Disabled = true; } - - MachineSprite.SetEntity(_machinePreview); } private string GetCostString(Dictionary costs) @@ -149,7 +132,7 @@ private string GetCostString(Dictionary costs) ("amount", amountText), ("material", Loc.GetString(matProto.Name))); - msg.AddMarkup(text); + msg.TryAddMarkup(text, out _); if (i != orderedCosts.Length - 1) msg.PushNewline(); @@ -157,12 +140,4 @@ private string GetCostString(Dictionary costs) return msg.ToMarkup(); } - - public override void Close() - { - base.Close(); - - _entityManager.DeleteEntity(_machinePreview); - _machinePreview = null; - } } diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 910c4c04376..0c9f2650bc4 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -3,11 +3,9 @@ using Content.Client.Changelog; using Content.Client.Chat.Managers; using Content.Client.Eui; -using Content.Client.Flash; using Content.Client.Fullscreen; using Content.Client.GhostKick; using Content.Client.Guidebook; -using Content.Client.Info; using Content.Client.Input; using Content.Client.IoC; using Content.Client.Launcher; @@ -53,7 +51,6 @@ public sealed class EntryPoint : GameClient [Dependency] private readonly IScreenshotHook _screenshotHook = default!; [Dependency] private readonly FullscreenHook _fullscreenHook = default!; [Dependency] private readonly ChangelogManager _changelogManager = default!; - [Dependency] private readonly RulesManager _rulesManager = default!; [Dependency] private readonly ViewportManager _viewportManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; @@ -128,7 +125,6 @@ public override void Init() _screenshotHook.Initialize(); _fullscreenHook.Initialize(); _changelogManager.Initialize(); - _rulesManager.Initialize(); _viewportManager.Initialize(); _ghostKick.Initialize(); _extendedDisconnectInformation.Initialize(); diff --git a/Content.Client/Guidebook/Components/GuideHelpComponent.cs b/Content.Client/Guidebook/Components/GuideHelpComponent.cs index db19bb9dcc8..bb1d30bbc16 100644 --- a/Content.Client/Guidebook/Components/GuideHelpComponent.cs +++ b/Content.Client/Guidebook/Components/GuideHelpComponent.cs @@ -1,4 +1,5 @@ -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; +using Content.Shared.Guidebook; +using Robust.Shared.Prototypes; namespace Content.Client.Guidebook.Components; @@ -13,9 +14,8 @@ public sealed partial class GuideHelpComponent : Component /// What guides to include show when opening the guidebook. The first entry will be used to select the currently /// selected guidebook. /// - [DataField("guides", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)] - [ViewVariables] - public List Guides = new(); + [DataField(required: true)] + public List> Guides = new(); /// /// Whether or not to automatically include the children of the given guides. diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml index 8dbfde3c475..cc6cc6e82b0 100644 --- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml +++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml @@ -18,7 +18,7 @@ Name="SearchBar" PlaceHolder="{Loc 'guidebook-filter-placeholder-text'}" HorizontalExpand="True" - Margin="0 5 10 5"> + Margin="0 5 10 5"> diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs index 4776386c1dd..3a67dca89da 100644 --- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs +++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs @@ -1,15 +1,14 @@ -using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Client.Guidebook.RichText; using Content.Client.UserInterface.ControlExtensions; using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls.FancyTree; -using JetBrains.Annotations; +using Content.Shared.Guidebook; using Robust.Client.AutoGenerated; -using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.ContentPack; +using Robust.Shared.Prototypes; namespace Content.Client.Guidebook.Controls; @@ -19,7 +18,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler [Dependency] private readonly IResourceManager _resourceManager = default!; [Dependency] private readonly DocumentParsingManager _parsingMan = default!; - private Dictionary _entries = new(); + private Dictionary, GuideEntry> _entries = new(); public GuidebookWindow() { @@ -69,10 +68,10 @@ private void ShowGuide(GuideEntry entry) } public void UpdateGuides( - Dictionary entries, - List? rootEntries = null, - string? forceRoot = null, - string? selected = null) + Dictionary, GuideEntry> entries, + List>? rootEntries = null, + ProtoId? forceRoot = null, + ProtoId? selected = null) { _entries = entries; RepopulateTree(rootEntries, forceRoot); @@ -98,11 +97,11 @@ public void UpdateGuides( } } - private IEnumerable GetSortedEntries(List? rootEntries) + private IEnumerable GetSortedEntries(List>? rootEntries) { if (rootEntries == null) { - HashSet entries = new(_entries.Keys); + HashSet> entries = new(_entries.Keys); foreach (var entry in _entries.Values) { if (entry.Children.Count > 0) @@ -111,7 +110,7 @@ private IEnumerable GetSortedEntries(List? rootEntries) .Select(childId => _entries[childId]) .OrderBy(childEntry => childEntry.Priority) .ThenBy(childEntry => Loc.GetString(childEntry.Name)) - .Select(childEntry => childEntry.Id) + .Select(childEntry => new ProtoId(childEntry.Id)) .ToList(); entry.Children = sortedChildren; @@ -127,13 +126,13 @@ private IEnumerable GetSortedEntries(List? rootEntries) .ThenBy(rootEntry => Loc.GetString(rootEntry.Name)); } - private void RepopulateTree(List? roots = null, string? forcedRoot = null) + private void RepopulateTree(List>? roots = null, ProtoId? forcedRoot = null) { Tree.Clear(); - HashSet addedEntries = new(); + HashSet> addedEntries = new(); - TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot, null, addedEntries); + TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries); foreach (var entry in GetSortedEntries(roots)) { AddEntry(entry.Id, parent, addedEntries); @@ -141,13 +140,15 @@ private void RepopulateTree(List? roots = null, string? forcedRoot = nul Tree.SetAllExpanded(true); } - private TreeItem? AddEntry(string id, TreeItem? parent, HashSet addedEntries) + private TreeItem? AddEntry(ProtoId id, TreeItem? parent, HashSet> addedEntries) { if (!_entries.TryGetValue(id, out var entry)) return null; if (!addedEntries.Add(id)) { + // TODO GUIDEBOOK Maybe allow duplicate entries? + // E.g., for adding medicine under both chemicals & the chemist job Logger.Error($"Adding duplicate guide entry: {id}"); return null; } diff --git a/Content.Client/Guidebook/DocumentParsingManager.cs b/Content.Client/Guidebook/DocumentParsingManager.cs index b81866a6260..e8a0743b9e0 100644 --- a/Content.Client/Guidebook/DocumentParsingManager.cs +++ b/Content.Client/Guidebook/DocumentParsingManager.cs @@ -1,7 +1,10 @@ using System.Linq; using Content.Client.Guidebook.Richtext; +using Content.Shared.Guidebook; using Pidgin; using Robust.Client.UserInterface; +using Robust.Shared.ContentPack; +using Robust.Shared.Prototypes; using Robust.Shared.Reflection; using Robust.Shared.Sandboxing; using static Pidgin.Parser; @@ -13,7 +16,9 @@ namespace Content.Client.Guidebook; /// public sealed partial class DocumentParsingManager { + [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IReflectionManager _reflectionManager = default!; + [Dependency] private readonly IResourceManager _resourceManager = default!; [Dependency] private readonly ISandboxHelper _sandboxHelper = default!; private readonly Dictionary> _tagControlParsers = new(); @@ -37,6 +42,21 @@ public void Initialize() ControlParser = SkipWhitespaces.Then(_controlParser.Many()); } + public bool TryAddMarkup(Control control, ProtoId entryId, bool log = true) + { + if (!_prototype.TryIndex(entryId, out var entry)) + return false; + + using var file = _resourceManager.ContentFileReadText(entry.Text); + return TryAddMarkup(control, file.ReadToEnd(), log); + } + + public bool TryAddMarkup(Control control, GuideEntry entry, bool log = true) + { + using var file = _resourceManager.ContentFileReadText(entry.Text); + return TryAddMarkup(control, file.ReadToEnd(), log); + } + public bool TryAddMarkup(Control control, string text, bool log = true) { try diff --git a/Content.Client/Guidebook/GuidebookSystem.cs b/Content.Client/Guidebook/GuidebookSystem.cs index 86dcf769424..675a025d7a8 100644 --- a/Content.Client/Guidebook/GuidebookSystem.cs +++ b/Content.Client/Guidebook/GuidebookSystem.cs @@ -2,6 +2,7 @@ using Content.Client.Guidebook.Components; using Content.Client.Light; using Content.Client.Verbs; +using Content.Shared.Guidebook; using Content.Shared.Interaction; using Content.Shared.Light.Components; using Content.Shared.Speech; @@ -13,6 +14,7 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Player; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -31,7 +33,12 @@ public sealed class GuidebookSystem : EntitySystem [Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!; [Dependency] private readonly TagSystem _tags = default!; - public event Action, List?, string?, bool, string?>? OnGuidebookOpen; + public event Action>, + List>?, + ProtoId?, + bool, + ProtoId?>? OnGuidebookOpen; + public const string GuideEmbedTag = "GuideEmbeded"; private EntityUid _defaultUser; @@ -80,7 +87,7 @@ private void OnGetVerbs(EntityUid uid, GuideHelpComponent component, GetVerbsEve }); } - public void OpenHelp(List guides) + public void OpenHelp(List> guides) { OnGuidebookOpen?.Invoke(guides, null, null, true, guides[0]); } diff --git a/Content.Client/Info/InfoSystem.cs b/Content.Client/Info/InfoSystem.cs deleted file mode 100644 index b6979949818..00000000000 --- a/Content.Client/Info/InfoSystem.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Content.Shared.Info; -using Robust.Shared.Log; - -namespace Content.Client.Info; - -public sealed class InfoSystem : EntitySystem -{ - public RulesMessage Rules = new RulesMessage("Server Rules", "The server did not send any rules."); - [Dependency] private readonly RulesManager _rules = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeNetworkEvent(OnRulesReceived); - Log.Debug("Requested server info."); - RaiseNetworkEvent(new RequestRulesMessage()); - } - - private void OnRulesReceived(RulesMessage message, EntitySessionEventArgs eventArgs) - { - Log.Debug("Received server rules."); - Rules = message; - _rules.UpdateRules(); - } -} diff --git a/Content.Client/Info/RulesAndInfoWindow.cs b/Content.Client/Info/RulesAndInfoWindow.cs index 7a763a1d6f4..b9131dcb3c0 100644 --- a/Content.Client/Info/RulesAndInfoWindow.cs +++ b/Content.Client/Info/RulesAndInfoWindow.cs @@ -1,10 +1,8 @@ using System.Numerics; using Content.Client.UserInterface.Systems.EscapeMenu; -using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.Configuration; using Robust.Shared.ContentPack; namespace Content.Client.Info @@ -12,7 +10,6 @@ namespace Content.Client.Info public sealed class RulesAndInfoWindow : DefaultWindow { [Dependency] private readonly IResourceManager _resourceManager = default!; - [Dependency] private readonly RulesManager _rules = default!; public RulesAndInfoWindow() { @@ -22,8 +19,14 @@ public RulesAndInfoWindow() var rootContainer = new TabContainer(); - var rulesList = new Info(); - var tutorialList = new Info(); + var rulesList = new RulesControl + { + Margin = new Thickness(10) + }; + var tutorialList = new Info + { + Margin = new Thickness(10) + }; rootContainer.AddChild(rulesList); rootContainer.AddChild(tutorialList); @@ -31,7 +34,6 @@ public RulesAndInfoWindow() TabContainer.SetTabTitle(rulesList, Loc.GetString("ui-info-tab-rules")); TabContainer.SetTabTitle(tutorialList, Loc.GetString("ui-info-tab-tutorial")); - AddSection(rulesList, _rules.RulesSection()); PopulateTutorial(tutorialList); Contents.AddChild(rootContainer); diff --git a/Content.Client/Info/RulesControl.xaml b/Content.Client/Info/RulesControl.xaml index 3b247646884..04fa7191234 100644 --- a/Content.Client/Info/RulesControl.xaml +++ b/Content.Client/Info/RulesControl.xaml @@ -1,6 +1,18 @@ - + + + + + + +