]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Give jobs & antags prototypes a guide field (#28614)
authorLeon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Thu, 6 Jun 2024 12:05:58 +0000 (00:05 +1200)
committerGitHub <noreply@github.com>
Thu, 6 Jun 2024 12:05:58 +0000 (05:05 -0700)
* Give jobs & antags prototypes a guide field

* A

* space

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
* Add todo

* Fix merge errors

---------

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
24 files changed:
Content.Client/Guidebook/Components/GuideHelpComponent.cs
Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
Content.Client/Guidebook/DocumentParsingManager.cs
Content.Client/Guidebook/GuidebookSystem.cs
Content.Client/Info/RulesControl.xaml.cs
Content.Client/Lobby/LobbyUIController.cs
Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
Content.Client/Lobby/UI/Roles/RequirementsSelector.xaml
Content.Client/Lobby/UI/Roles/RequirementsSelector.xaml.cs
Content.Client/Stylesheets/StyleNano.cs
Content.Client/UserInterface/Controls/FancyWindow.xaml.cs
Content.Client/UserInterface/Systems/Guidebook/GuidebookUIController.cs
Content.Client/UserInterface/Systems/Info/InfoUIController.cs
Content.IntegrationTests/Tests/Guidebook/GuideEntryPrototypeTests.cs
Content.Server/Entry/EntryPoint.cs
Content.Shared/Guidebook/GuideEntry.cs [moved from Content.Client/Guidebook/GuideEntry.cs with 65% similarity]
Content.Shared/Roles/AntagPrototype.cs
Content.Shared/Roles/JobPrototype.cs
Resources/Prototypes/Guidebook/medical.yml
Resources/Prototypes/Roles/Antags/ninja.yml
Resources/Prototypes/Roles/Antags/nukeops.yml
Resources/Prototypes/Roles/Antags/revolutionary.yml
Resources/Prototypes/Roles/Antags/traitor.yml
Resources/Prototypes/Roles/Antags/zombie.yml

index db19bb9dcc89717344769a2ed9a8acced0f2a999..bb1d30bbc165971eae9d676fdf56dadee97b1396 100644 (file)
@@ -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.
     /// </summary>
-    [DataField("guides", customTypeSerializer: typeof(PrototypeIdListSerializer<GuideEntryPrototype>), required: true)]
-    [ViewVariables]
-    public List<string> Guides = new();
+    [DataField(required: true)]
+    public List<ProtoId<GuideEntryPrototype>> Guides = new();
 
     /// <summary>
     /// Whether or not to automatically include the children of the given guides.
index 4776386c1dd75e0afcbe13449bec1edf0bda54ea..3a67dca89da8ba910312ad3fa349cb04c63b4906 100644 (file)
@@ -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<string, GuideEntry> _entries = new();
+    private Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> _entries = new();
 
     public GuidebookWindow()
     {
@@ -69,10 +68,10 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
     }
 
     public void UpdateGuides(
-        Dictionary<string, GuideEntry> entries,
-        List<string>? rootEntries = null,
-        string? forceRoot = null,
-        string? selected = null)
+        Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> entries,
+        List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
+        ProtoId<GuideEntryPrototype>? forceRoot = null,
+        ProtoId<GuideEntryPrototype>? selected = null)
     {
         _entries = entries;
         RepopulateTree(rootEntries, forceRoot);
@@ -98,11 +97,11 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
         }
     }
 
-    private IEnumerable<GuideEntry> GetSortedEntries(List<string>? rootEntries)
+    private IEnumerable<GuideEntry> GetSortedEntries(List<ProtoId<GuideEntryPrototype>>? rootEntries)
     {
         if (rootEntries == null)
         {
-            HashSet<string> entries = new(_entries.Keys);
+            HashSet<ProtoId<GuideEntryPrototype>> entries = new(_entries.Keys);
             foreach (var entry in _entries.Values)
             {
                 if (entry.Children.Count > 0)
@@ -111,7 +110,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
                         .Select(childId => _entries[childId])
                         .OrderBy(childEntry => childEntry.Priority)
                         .ThenBy(childEntry => Loc.GetString(childEntry.Name))
-                        .Select(childEntry => childEntry.Id)
+                        .Select(childEntry => new ProtoId<GuideEntryPrototype>(childEntry.Id))
                         .ToList();
 
                     entry.Children = sortedChildren;
@@ -127,13 +126,13 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
             .ThenBy(rootEntry => Loc.GetString(rootEntry.Name));
     }
 
-    private void RepopulateTree(List<string>? roots = null, string? forcedRoot = null)
+    private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null, ProtoId<GuideEntryPrototype>? forcedRoot = null)
     {
         Tree.Clear();
 
-        HashSet<string> addedEntries = new();
+        HashSet<ProtoId<GuideEntryPrototype>> 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 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
         Tree.SetAllExpanded(true);
     }
 
-    private TreeItem? AddEntry(string id, TreeItem? parent, HashSet<string> addedEntries)
+    private TreeItem? AddEntry(ProtoId<GuideEntryPrototype> id, TreeItem? parent, HashSet<ProtoId<GuideEntryPrototype>> 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;
         }
index 9c9e569eb417e99596f67438b5eb16b013a9f504..e8a0743b9e05e69d51de88f3be3858a47478a539 100644 (file)
@@ -1,5 +1,6 @@
 using System.Linq;
 using Content.Client.Guidebook.Richtext;
+using Content.Shared.Guidebook;
 using Pidgin;
 using Robust.Client.UserInterface;
 using Robust.Shared.ContentPack;
index 86dcf76942487f7c8f6e6a0a9dacd0c453a18f08..675a025d7a860fd4d94bca1ff7ab892b3e7b335a 100644 (file)
@@ -2,6 +2,7 @@ using System.Linq;
 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;
 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>, List<string>?, string?, bool, string?>? OnGuidebookOpen;
+    public event Action<List<ProtoId<GuideEntryPrototype>>,
+        List<ProtoId<GuideEntryPrototype>>?,
+        ProtoId<GuideEntryPrototype>?,
+        bool,
+        ProtoId<GuideEntryPrototype>?>? OnGuidebookOpen;
+
     public const string GuideEmbedTag = "GuideEmbeded";
 
     private EntityUid _defaultUser;
@@ -80,7 +87,7 @@ public sealed class GuidebookSystem : EntitySystem
         });
     }
 
-    public void OpenHelp(List<string> guides)
+    public void OpenHelp(List<ProtoId<GuideEntryPrototype>> guides)
     {
         OnGuidebookOpen?.Invoke(guides, null, null, true, guides[0]);
     }
index 5e259074968798a6e37c4a8405e638125d65e5cf..22a520d539cbb22fc3e64b87dfa94a853584b374 100644 (file)
@@ -1,6 +1,7 @@
 using Content.Client.Guidebook;
 using Content.Client.Guidebook.RichText;
 using Content.Client.UserInterface.Systems.Info;
+using Content.Shared.Guidebook;
 using Robust.Client.AutoGenerated;
 using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.XAML;
index 05b98606ab9924080ab89caebfa6c2de04ac1ecb..aa66b7731d1541f9a12b60204e77493e460efc10 100644 (file)
@@ -1,4 +1,5 @@
 using System.Linq;
+using Content.Client.Guidebook;
 using Content.Client.Humanoid;
 using Content.Client.Inventory;
 using Content.Client.Lobby.UI;
@@ -41,6 +42,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
     [UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
     [UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
     [UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
+    [UISystemDependency] private readonly GuidebookSystem _guide = default!;
 
     private CharacterSetupGui? _characterSetup;
     private HumanoidProfileEditor? _profileEditor;
@@ -232,6 +234,8 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
             _requirements,
             _markings);
 
+        _profileEditor.OnOpenGuidebook += _guide.OpenHelp;
+
         _characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
 
         _characterSetup.CloseButton.OnPressed += _ =>
index ac43fa11a8534c3308ff9f629374af2956d5a800..1681cd93bd2bb0c0737badb29274f92edd114dec 100644 (file)
@@ -1,7 +1,6 @@
 using System.IO;
 using System.Linq;
 using System.Numerics;
-using Content.Client.Guidebook;
 using Content.Client.Humanoid;
 using Content.Client.Lobby.UI.Loadouts;
 using Content.Client.Lobby.UI.Roles;
@@ -12,13 +11,13 @@ using Content.Client.UserInterface.Systems.Guidebook;
 using Content.Shared.CCVar;
 using Content.Shared.Clothing;
 using Content.Shared.GameTicking;
+using Content.Shared.Guidebook;
 using Content.Shared.Humanoid;
 using Content.Shared.Humanoid.Markings;
 using Content.Shared.Humanoid.Prototypes;
 using Content.Shared.Preferences;
 using Content.Shared.Preferences.Loadouts;
 using Content.Shared.Roles;
-using Content.Shared.StatusIcon;
 using Content.Shared.Traits;
 using Robust.Client.AutoGenerated;
 using Robust.Client.Graphics;
@@ -96,6 +95,8 @@ namespace Content.Client.Lobby.UI
         [ValidatePrototypeId<GuideEntryPrototype>]
         private const string DefaultSpeciesGuidebook = "Species";
 
+        public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
+
         private ISawmill _sawmill;
 
         public HumanoidProfileEditor(
@@ -615,10 +616,11 @@ namespace Content.Client.Lobby.UI
                 {
                     Margin = new Thickness(3f, 3f, 3f, 0f),
                 };
+                selector.OnOpenGuidebook += OnOpenGuidebook;
 
                 var title = Loc.GetString(antag.Name);
                 var description = Loc.GetString(antag.Objective);
-                selector.Setup(items, title, 250, description);
+                selector.Setup(items, title, 250, description, guides: antag.Guides);
                 selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
 
                 if (!_requirements.CheckRoleTime(antag.Requirements, out var reason))
@@ -753,6 +755,10 @@ namespace Content.Client.Lobby.UI
 
         private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
         {
+            // TODO GUIDEBOOK
+            // make the species guide book a field on the species prototype.
+            // I.e., do what jobs/antags do.
+
             var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
             var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
             var page = DefaultSpeciesGuidebook;
@@ -761,10 +767,10 @@ namespace Content.Client.Lobby.UI
 
             if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot))
             {
-                var dict = new Dictionary<string, GuideEntry>();
+                var dict = new Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>();
                 dict.Add(DefaultSpeciesGuidebook, guideRoot);
                 //TODO: Don't close the guidebook if its already open, just go to the correct page
-                guidebookController.ToggleGuidebook(dict, includeChildren:true, selected: page);
+                guidebookController.OpenGuidebook(dict, includeChildren:true, selected: page);
             }
         }
 
@@ -859,6 +865,7 @@ namespace Content.Client.Lobby.UI
                     {
                         Margin = new Thickness(3f, 3f, 3f, 0f),
                     };
+                    selector.OnOpenGuidebook += OnOpenGuidebook;
 
                     var icon = new TextureRect
                     {
@@ -867,7 +874,7 @@ namespace Content.Client.Lobby.UI
                     };
                     var jobIcon = _prototypeManager.Index(job.Icon);
                     icon.Texture = jobIcon.Icon.Frame0();
-                    selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon);
+                    selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
 
                     if (!_requirements.IsAllowed(job, out var reason))
                     {
index 88bf49b123aa4d2d2c9a51593735dd95f1a79a79..4cab5286c2474094eb828cb632c6063680b6292d 100644 (file)
@@ -1,9 +1,14 @@
 <BoxContainer xmlns="https://spacestation14.io"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          Orientation="Horizontal">
-        <Label Name="TitleLabel"
-                      Margin="5 0"
-                      MouseFilter="Stop"/>
-        <BoxContainer Name="OptionsContainer"
-                      SetWidth="400"/>
+    <Label Name="TitleLabel"
+           Margin="5 0"
+           MouseFilter="Stop"/>
+
+    <!--21 was the height of OptionsContainer at the time that this button was added. So I am limiting the texture to 21x21-->
+    <Control SetSize="21 21">
+        <TextureButton Name="Help" StyleClasses="HelpButton"/>
+    </Control>
+    <BoxContainer Name="OptionsContainer"
+                  SetWidth="400"/>
 </BoxContainer>
index 8b4b21ba9c789318ed2a519e13135f0dbc7c4aac..ce75537355c9ec801f150a64d6daeaad516caba9 100644 (file)
@@ -1,10 +1,12 @@
 using System.Numerics;
 using Content.Client.Stylesheets;
 using Content.Client.UserInterface.Controls;
+using Content.Shared.Guidebook;
 using Robust.Client.AutoGenerated;
 using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.CustomControls;
 using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
 
 namespace Content.Client.Lobby.UI.Roles;
@@ -17,8 +19,10 @@ public sealed partial class RequirementsSelector : BoxContainer
 {
     private readonly RadioOptions<int> _options;
     private readonly StripeBack _lockStripe;
+    private List<ProtoId<GuideEntryPrototype>>? _guides;
 
     public event Action<int>? OnSelected;
+    public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
 
     public int Selected => _options.SelectedId;
 
@@ -60,18 +64,33 @@ public sealed partial class RequirementsSelector : BoxContainer
                 requirementsLabel
             }
         };
+
+        Help.OnPressed += _ =>
+        {
+            if (_guides != null)
+                OnOpenGuidebook?.Invoke(_guides);
+        };
     }
 
     /// <summary>
     /// Actually adds the controls.
     /// </summary>
-    public void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
+    public void Setup(
+        (string, int)[] items,
+        string title,
+        int titleSize,
+        string? description,
+        TextureRect? icon = null,
+        List<ProtoId<GuideEntryPrototype>>? guides = null)
     {
         foreach (var (text, value) in items)
         {
             _options.AddItem(Loc.GetString(text), value);
         }
 
+        Help.Visible = guides != null;
+        _guides = guides;
+
         TitleLabel.Text = title;
         TitleLabel.MinSize = new Vector2(titleSize, 0f);
         TitleLabel.ToolTip = description;
index 6a5e0d82a20333bc819e4b987c8e26527096e1e6..b9a4e63f31592ba80c47f3999c8135e28c3e4c12 100644 (file)
@@ -78,6 +78,8 @@ namespace Content.Client.Stylesheets
         public const string StyleClassLabelSmall = "LabelSmall";
         public const string StyleClassButtonBig = "ButtonBig";
 
+        public const string StyleClassButtonHelp = "HelpButton";
+
         public const string StyleClassPopupMessageSmall = "PopupMessageSmall";
         public const string StyleClassPopupMessageSmallCaution = "PopupMessageSmallCaution";
         public const string StyleClassPopupMessageMedium = "PopupMessageMedium";
@@ -1346,6 +1348,10 @@ namespace Content.Client.Stylesheets
                     new StyleProperty(PanelContainer.StylePropertyPanel, new StyleBoxFlat { BackgroundColor = NanoGold, ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2}),
                 }),
 
+                Element<TextureButton>()
+                    .Class(StyleClassButtonHelp)
+                    .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
+
                 // Labels ---
                 Element<Label>().Class(StyleClassLabelBig)
                     .Prop(Label.StylePropertyFont, notoSans16),
index 5912687fc358f908472a37dd17c3550eaf17cf4d..df6501dcb866b9eff8f363f8980a4008cbe3a5a9 100644 (file)
@@ -1,6 +1,7 @@
 using System.Numerics;
 using Content.Client.Guidebook;
 using Content.Client.Guidebook.Components;
+using Content.Shared.Guidebook;
 using Robust.Client.AutoGenerated;
 using Robust.Client.UserInterface.CustomControls;
 using Robust.Client.UserInterface.XAML;
@@ -32,8 +33,8 @@ namespace Content.Client.UserInterface.Controls
             set => WindowTitle.Text = value;
         }
 
-        private List<string>? _helpGuidebookIds;
-        public List<string>? HelpGuidebookIds
+        private List<ProtoId<GuideEntryPrototype>>? _helpGuidebookIds;
+        public List<ProtoId<GuideEntryPrototype>>? HelpGuidebookIds
         {
             get => _helpGuidebookIds;
             set
index 6f20393b7f240eae30ac9f627f00c8fd4889c1d0..e30a6b3a215b011851b85ec9dc885ba636c599ea 100644 (file)
@@ -4,6 +4,7 @@ using Content.Client.Guidebook;
 using Content.Client.Guidebook.Controls;
 using Content.Client.Lobby;
 using Content.Client.UserInterface.Controls;
+using Content.Shared.Guidebook;
 using Content.Shared.Input;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controllers;
@@ -74,12 +75,12 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
 
     public void OnSystemLoaded(GuidebookSystem system)
     {
-        _guidebookSystem.OnGuidebookOpen += ToggleGuidebook;
+        _guidebookSystem.OnGuidebookOpen += OpenGuidebook;
     }
 
     public void OnSystemUnloaded(GuidebookSystem system)
     {
-        _guidebookSystem.OnGuidebookOpen -= ToggleGuidebook;
+        _guidebookSystem.OnGuidebookOpen -= OpenGuidebook;
     }
 
     internal void UnloadButton()
@@ -103,6 +104,22 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
         ToggleGuidebook();
     }
 
+    public void ToggleGuidebook()
+    {
+        if (_guideWindow == null)
+            return;
+
+        if (_guideWindow.IsOpen)
+        {
+            UIManager.ClickSound();
+            _guideWindow.Close();
+        }
+        else
+        {
+            OpenGuidebook();
+        }
+    }
+
     private void OnWindowClosed()
     {
         if (GuidebookButton != null)
@@ -127,30 +144,23 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
     /// <param name="includeChildren">Whether or not to automatically include child entries. If false, this will ONLY
     /// show the specified entries</param>
     /// <param name="selected">The guide whose contents should be displayed when the guidebook is opened</param>
-    public void ToggleGuidebook(
-        Dictionary<string, GuideEntry>? guides = null,
-        List<string>? rootEntries = null,
-        string? forceRoot = null,
+    public void OpenGuidebook(
+        Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>? guides = null,
+        List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
+        ProtoId<GuideEntryPrototype>? forceRoot = null,
         bool includeChildren = true,
-        string? selected = null)
+        ProtoId<GuideEntryPrototype>? selected = null)
     {
         if (_guideWindow == null)
             return;
 
-        if (_guideWindow.IsOpen)
-        {
-            UIManager.ClickSound();
-            _guideWindow.Close();
-            return;
-        }
-
         if (GuidebookButton != null)
             GuidebookButton.SetClickPressed(!_guideWindow.IsOpen);
 
         if (guides == null)
         {
             guides = _prototypeManager.EnumeratePrototypes<GuideEntryPrototype>()
-                .ToDictionary(x => x.ID, x => (GuideEntry) x);
+                .ToDictionary(x => new ProtoId<GuideEntryPrototype>(x.ID), x => (GuideEntry) x);
         }
         else if (includeChildren)
         {
@@ -171,17 +181,17 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
         _guideWindow.OpenCenteredRight();
     }
 
-    public void ToggleGuidebook(
-        List<string> guideList,
-        List<string>? rootEntries = null,
-        string? forceRoot = null,
+    public void OpenGuidebook(
+        List<ProtoId<GuideEntryPrototype>> guideList,
+        List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
+        ProtoId<GuideEntryPrototype>? forceRoot = null,
         bool includeChildren = true,
-        string? selected = null)
+        ProtoId<GuideEntryPrototype>? selected = null)
     {
-        Dictionary<string, GuideEntry> guides = new();
+        Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> guides = new();
         foreach (var guideId in guideList)
         {
-            if (!_prototypeManager.TryIndex<GuideEntryPrototype>(guideId, out var guide))
+            if (!_prototypeManager.TryIndex(guideId, out var guide))
             {
                 Logger.Error($"Encountered unknown guide prototype: {guideId}");
                 continue;
@@ -189,17 +199,29 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
             guides.Add(guideId, guide);
         }
 
-        ToggleGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
+        OpenGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
+    }
+
+    public void CloseGuidebook()
+    {
+        if (_guideWindow == null)
+            return;
+
+        if (_guideWindow.IsOpen)
+        {
+            UIManager.ClickSound();
+            _guideWindow.Close();
+        }
     }
 
-    private void RecursivelyAddChildren(GuideEntry guide, Dictionary<string, GuideEntry> guides)
+    private void RecursivelyAddChildren(GuideEntry guide, Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> guides)
     {
         foreach (var childId in guide.Children)
         {
             if (guides.ContainsKey(childId))
                 continue;
 
-            if (!_prototypeManager.TryIndex<GuideEntryPrototype>(childId, out var child))
+            if (!_prototypeManager.TryIndex(childId, out var child))
             {
                 Logger.Error($"Encountered unknown guide prototype: {childId} as a child of {guide.Id}. If the child is not a prototype, it must be directly provided.");
                 continue;
index 6d398ec6aa56fa2c9600dacdec7eed7f3b44a1cf..01dd783e6e07ed0e832019d95ed37ef49f52882d 100644 (file)
@@ -1,9 +1,9 @@
 using System.Globalization;
 using Content.Client.Gameplay;
-using Content.Client.Guidebook;
 using Content.Client.Info;
 using Content.Shared.Administration.Managers;
 using Content.Shared.CCVar;
+using Content.Shared.Guidebook;
 using Content.Shared.Info;
 using Robust.Client;
 using Robust.Client.Console;
index b56191525f871ff3975123feb18154ce60a8aac8..ae64eace9a2a5ece042634c32720854960973a05 100644 (file)
@@ -3,6 +3,7 @@ using Content.Client.Guidebook.Richtext;
 using Robust.Shared.ContentPack;
 using Robust.Shared.Prototypes;
 using System.Linq;
+using Content.Shared.Guidebook;
 
 namespace Content.IntegrationTests.Tests.Guidebook;
 
index 4be2c1787171d624b0af2bd678a526c433bec285..158ec23cce3fcfd133a15e2e3cae483d634d0e52 100644 (file)
@@ -67,7 +67,6 @@ namespace Content.Server.Entry
             factory.RegisterIgnore(IgnoredComponents.List);
 
             prototypes.RegisterIgnore("parallax");
-            prototypes.RegisterIgnore("guideEntry");
 
             ServerContentIoC.Register();
 
similarity index 65%
rename from Content.Client/Guidebook/GuideEntry.cs
rename to Content.Shared/Guidebook/GuideEntry.cs
index b7b3b3309e6d77475b50b5668a379511429f45a3..0e0e166cd55aae47310d9b0b74c0704149af90de 100644 (file)
@@ -1,8 +1,13 @@
 using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
 using Robust.Shared.Utility;
 
-namespace Content.Client.Guidebook;
+namespace Content.Shared.Guidebook;
+
+[Prototype("guideEntry")]
+public sealed partial class GuideEntryPrototype : GuideEntry, IPrototype
+{
+    public string ID => Id;
+}
 
 [Virtual]
 public class GuideEntry
@@ -10,7 +15,7 @@ public class GuideEntry
     /// <summary>
     ///     The file containing the contents of this guide.
     /// </summary>
-    [DataField("text", required: true)] public ResPath Text = default!;
+    [DataField(required: true)] public ResPath Text = default!;
 
     /// <summary>
     ///     The unique id for this guide.
@@ -21,28 +26,22 @@ public class GuideEntry
     /// <summary>
     ///     The name of this guide. This gets localized.
     /// </summary>
-    [DataField("name", required: true)] public string Name = default!;
+    [DataField(required: true)] public string Name = default!;
 
     /// <summary>
     ///     The "children" of this guide for when guides are shown in a tree / table of contents.
     /// </summary>
-    [DataField("children", customTypeSerializer:typeof(PrototypeIdListSerializer<GuideEntryPrototype>))]
-    public List<string> Children = new();
+    [DataField]
+    public List<ProtoId<GuideEntryPrototype>> Children = new();
 
     /// <summary>
     ///     Enable filtering of items.
     /// </summary>
-    [DataField("filterEnabled")] public bool FilterEnabled = default!;
+    [DataField] public bool FilterEnabled = default!;
 
     /// <summary>
     ///     Priority for sorting top-level guides when shown in a tree / table of contents.
     ///     If the guide is the child of some other guide, the order simply determined by the order of children in <see cref="Children"/>.
     /// </summary>
-    [DataField("priority")] public int Priority = 0;
-}
-
-[Prototype("guideEntry")]
-public sealed partial class GuideEntryPrototype : GuideEntry, IPrototype
-{
-    public string ID => Id;
+    [DataField] public int Priority = 0;
 }
index c6acb9b75756b9581055c7c4616fbeedaf91bade..05c0c535049cf15c49d0abed663430ba69cf02dd 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Shared.Guidebook;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 
@@ -43,4 +44,11 @@ public sealed partial class AntagPrototype : IPrototype
     /// </summary>
     [DataField("requirements")]
     public HashSet<JobRequirement>? Requirements;
+
+    /// <summary>
+    /// Optional list of guides associated with this antag. If the guides are opened, the first entry in this list
+    /// will be used to select the currently selected guidebook.
+    /// </summary>
+    [DataField]
+    public List<ProtoId<GuideEntryPrototype>>? Guides;
 }
index dd67e7b10474f2a66017a5400daee6badc9739a6..2a8e9d4b0333f02580b52e3305f99b7556714458 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Shared.Access;
+using Content.Shared.Guidebook;
 using Content.Shared.Players.PlayTimeTracking;
 using Content.Shared.StatusIcon;
 using Robust.Shared.Prototypes;
@@ -117,6 +118,13 @@ namespace Content.Shared.Roles
 
         [DataField]
         public bool Whitelisted;
+
+        /// <summary>
+        /// Optional list of guides associated with this role. If the guides are opened, the first entry in this list
+        /// will be used to select the currently selected guidebook.
+        /// </summary>
+        [DataField]
+        public List<ProtoId<GuideEntryPrototype>>? Guides;
     }
 
     /// <summary>
index 95a4f1ca75f04e5cd8b8e7ad7e9a0e68e2a022bd..0c7b1c781e4d9b25af5a42af4074bf7bb38a0600 100644 (file)
@@ -28,7 +28,9 @@
   name: guide-entry-chemist
   text: "/ServerInfo/Guidebook/Medical/Chemist.xml"
   children:
-  - Medicine
+  # - Medicine
+  # Duplicate guide entries are currently not supported
+  # TODO GUIDEBOOK Maybe allow duplicate entries?
   - Botanicals
   - AdvancedBrute
 
index cb32dc2c102d74c50b4dfc0fbfd907ba1312acec..6df8a2317eb233d2daab8fd4b7c3bbd3c2c4296c 100644 (file)
@@ -4,6 +4,7 @@
   antagonist: true
   setPreference: false
   objective: roles-antag-space-ninja-objective
+  guides: [ SpaceNinja ]
 
 #Ninja Gear
 - type: startingGear
@@ -33,4 +34,4 @@
     - Screwdriver
     - Wirecutter
     - Welder
-    - Multitool
\ No newline at end of file
+    - Multitool
index e91d50f53cd97ed1a77955459a65bd566bb6104f..7cfed348f1835a99b71e8a782eb8e623d4ece3e5 100644 (file)
@@ -7,6 +7,7 @@
   requirements:
   - !type:OverallPlaytimeRequirement
     time: 18000 # 5h
+  guides: [ NuclearOperatives ]
 
 - type: antag
   id: NukeopsMedic
@@ -18,8 +19,9 @@
   - !type:OverallPlaytimeRequirement
     time: 18000 # 5h
   - !type:RoleTimeRequirement
-    role: JobChemist 
+    role: JobChemist
     time: 10800 # 3h
+  guides: [ NuclearOperatives ]
 
 - type: antag
   id: NukeopsCommander
@@ -34,6 +36,7 @@
     department: Security
     time: 18000 # 5h
   # should be changed to nukie playtime when thats tracked (wyci)
+  guides: [ NuclearOperatives ]
 
 #Nuclear Operative Gear
 - type: startingGear
@@ -96,4 +99,4 @@
   id: SyndicateLoneOperativeGearFull
   parent: SyndicateOperativeGearFull
   equipment:
-    pocket2: BaseUplinkRadio60TC
\ No newline at end of file
+    pocket2: BaseUplinkRadio60TC
index 3bdc9d7977c797c90eac556a02240e1924ab4d4d..502a68ea9e86e79e09fa8c9cf31db63008319c74 100644 (file)
@@ -4,6 +4,7 @@
   antagonist: true
   setPreference: true
   objective: roles-antag-rev-head-objective
+  guides: [ Revolutionaries ]
 
 - type: antag
   id: Rev
   antagonist: true
   setPreference: false
   objective: roles-antag-rev-objective
+  guides: [ Revolutionaries ]
 
 - type: startingGear
   id: HeadRevGear
   storage:
     back:
     - Flash
-    - ClothingEyesGlassesSunglasses
\ No newline at end of file
+    - ClothingEyesGlassesSunglasses
index f192733b7e1e7e71f9470c96b9f6ce677a82f8ca..e97ced1795e524e0910124efc9d964c13e9febe7 100644 (file)
@@ -4,6 +4,7 @@
   antagonist: true
   setPreference: true
   objective: roles-antag-syndicate-agent-objective
+  guides: [ Traitors ]
 
 # Syndicate Operative Outfit - Monkey
 - type: startingGear
@@ -36,4 +37,4 @@
     ears: ClothingHeadsetAltSyndicate
     gloves: ClothingHandsGlovesCombat
     pocket1: BaseUplinkRadio40TC
-    id: SyndiPDA
\ No newline at end of file
+    id: SyndiPDA
index 2ba5640891f7d598facf89c285facd3cc786bba4..bc3c2d7f224115d21a8aa3d6a9df78ccc62fd786 100644 (file)
@@ -4,6 +4,7 @@
   antagonist: true
   setPreference: true
   objective: roles-antag-initial-infected-objective
+  guides: [ Zombies ]
 
 - type: antag
   id: Zombie
@@ -11,3 +12,4 @@
   antagonist: true
   setPreference: false
   objective: roles-antag-zombie-objective
+  guides: [ Zombies ]