Give jobs & antags prototypes a guide field (#28614)

* 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>
This commit is contained in:
Leon Friedrich
2024-06-07 00:05:58 +12:00
committed by GitHub
parent 236d2e5337
commit e7f2ae52ab
24 changed files with 182 additions and 82 deletions

View 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; 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 /// What guides to include show when opening the guidebook. The first entry will be used to select the currently
/// selected guidebook. /// selected guidebook.
/// </summary> /// </summary>
[DataField("guides", customTypeSerializer: typeof(PrototypeIdListSerializer<GuideEntryPrototype>), required: true)] [DataField(required: true)]
[ViewVariables] public List<ProtoId<GuideEntryPrototype>> Guides = new();
public List<string> Guides = new();
/// <summary> /// <summary>
/// Whether or not to automatically include the children of the given guides. /// Whether or not to automatically include the children of the given guides.

View File

@@ -1,15 +1,14 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Client.Guidebook.RichText; using Content.Client.Guidebook.RichText;
using Content.Client.UserInterface.ControlExtensions; using Content.Client.UserInterface.ControlExtensions;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Controls.FancyTree; using Content.Client.UserInterface.Controls.FancyTree;
using JetBrains.Annotations; using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Controls; 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 IResourceManager _resourceManager = default!;
[Dependency] private readonly DocumentParsingManager _parsingMan = default!; [Dependency] private readonly DocumentParsingManager _parsingMan = default!;
private Dictionary<string, GuideEntry> _entries = new(); private Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> _entries = new();
public GuidebookWindow() public GuidebookWindow()
{ {
@@ -69,10 +68,10 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
} }
public void UpdateGuides( public void UpdateGuides(
Dictionary<string, GuideEntry> entries, Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> entries,
List<string>? rootEntries = null, List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
string? forceRoot = null, ProtoId<GuideEntryPrototype>? forceRoot = null,
string? selected = null) ProtoId<GuideEntryPrototype>? selected = null)
{ {
_entries = entries; _entries = entries;
RepopulateTree(rootEntries, forceRoot); 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) if (rootEntries == null)
{ {
HashSet<string> entries = new(_entries.Keys); HashSet<ProtoId<GuideEntryPrototype>> entries = new(_entries.Keys);
foreach (var entry in _entries.Values) foreach (var entry in _entries.Values)
{ {
if (entry.Children.Count > 0) if (entry.Children.Count > 0)
@@ -111,7 +110,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
.Select(childId => _entries[childId]) .Select(childId => _entries[childId])
.OrderBy(childEntry => childEntry.Priority) .OrderBy(childEntry => childEntry.Priority)
.ThenBy(childEntry => Loc.GetString(childEntry.Name)) .ThenBy(childEntry => Loc.GetString(childEntry.Name))
.Select(childEntry => childEntry.Id) .Select(childEntry => new ProtoId<GuideEntryPrototype>(childEntry.Id))
.ToList(); .ToList();
entry.Children = sortedChildren; entry.Children = sortedChildren;
@@ -127,13 +126,13 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
.ThenBy(rootEntry => Loc.GetString(rootEntry.Name)); .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(); 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)) foreach (var entry in GetSortedEntries(roots))
{ {
AddEntry(entry.Id, parent, addedEntries); AddEntry(entry.Id, parent, addedEntries);
@@ -141,13 +140,15 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
Tree.SetAllExpanded(true); 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)) if (!_entries.TryGetValue(id, out var entry))
return null; return null;
if (!addedEntries.Add(id)) 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}"); Logger.Error($"Adding duplicate guide entry: {id}");
return null; return null;
} }

View File

@@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using Content.Client.Guidebook.Richtext; using Content.Client.Guidebook.Richtext;
using Content.Shared.Guidebook;
using Pidgin; using Pidgin;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;

View File

@@ -2,6 +2,7 @@ using System.Linq;
using Content.Client.Guidebook.Components; using Content.Client.Guidebook.Components;
using Content.Client.Light; using Content.Client.Light;
using Content.Client.Verbs; using Content.Client.Verbs;
using Content.Shared.Guidebook;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Light.Components; using Content.Shared.Light.Components;
using Content.Shared.Speech; using Content.Shared.Speech;
@@ -13,6 +14,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -31,7 +33,12 @@ public sealed class GuidebookSystem : EntitySystem
[Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!; [Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!;
[Dependency] private readonly TagSystem _tags = 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"; public const string GuideEmbedTag = "GuideEmbeded";
private EntityUid _defaultUser; 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]); OnGuidebookOpen?.Invoke(guides, null, null, true, guides[0]);
} }

View File

@@ -1,6 +1,7 @@
using Content.Client.Guidebook; using Content.Client.Guidebook;
using Content.Client.Guidebook.RichText; using Content.Client.Guidebook.RichText;
using Content.Client.UserInterface.Systems.Info; using Content.Client.UserInterface.Systems.Info;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;

View File

@@ -1,4 +1,5 @@
using System.Linq; using System.Linq;
using Content.Client.Guidebook;
using Content.Client.Humanoid; using Content.Client.Humanoid;
using Content.Client.Inventory; using Content.Client.Inventory;
using Content.Client.Lobby.UI; 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 HumanoidAppearanceSystem _humanoid = default!;
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!; [UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!; [UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
[UISystemDependency] private readonly GuidebookSystem _guide = default!;
private CharacterSetupGui? _characterSetup; private CharacterSetupGui? _characterSetup;
private HumanoidProfileEditor? _profileEditor; private HumanoidProfileEditor? _profileEditor;
@@ -232,6 +234,8 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
_requirements, _requirements,
_markings); _markings);
_profileEditor.OnOpenGuidebook += _guide.OpenHelp;
_characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor); _characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
_characterSetup.CloseButton.OnPressed += _ => _characterSetup.CloseButton.OnPressed += _ =>

View File

@@ -1,7 +1,6 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Client.Guidebook;
using Content.Client.Humanoid; using Content.Client.Humanoid;
using Content.Client.Lobby.UI.Loadouts; using Content.Client.Lobby.UI.Loadouts;
using Content.Client.Lobby.UI.Roles; using Content.Client.Lobby.UI.Roles;
@@ -12,13 +11,13 @@ using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Clothing; using Content.Shared.Clothing;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Guidebook;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes; using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Content.Shared.Traits; using Content.Shared.Traits;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -96,6 +95,8 @@ namespace Content.Client.Lobby.UI
[ValidatePrototypeId<GuideEntryPrototype>] [ValidatePrototypeId<GuideEntryPrototype>]
private const string DefaultSpeciesGuidebook = "Species"; private const string DefaultSpeciesGuidebook = "Species";
public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
private ISawmill _sawmill; private ISawmill _sawmill;
public HumanoidProfileEditor( public HumanoidProfileEditor(
@@ -615,10 +616,11 @@ namespace Content.Client.Lobby.UI
{ {
Margin = new Thickness(3f, 3f, 3f, 0f), Margin = new Thickness(3f, 3f, 3f, 0f),
}; };
selector.OnOpenGuidebook += OnOpenGuidebook;
var title = Loc.GetString(antag.Name); var title = Loc.GetString(antag.Name);
var description = Loc.GetString(antag.Objective); 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); selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
if (!_requirements.CheckRoleTime(antag.Requirements, out var reason)) if (!_requirements.CheckRoleTime(antag.Requirements, out var reason))
@@ -753,6 +755,10 @@ namespace Content.Client.Lobby.UI
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args) 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 guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies; var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
var page = DefaultSpeciesGuidebook; var page = DefaultSpeciesGuidebook;
@@ -761,10 +767,10 @@ namespace Content.Client.Lobby.UI
if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot)) 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); dict.Add(DefaultSpeciesGuidebook, guideRoot);
//TODO: Don't close the guidebook if its already open, just go to the correct page //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), Margin = new Thickness(3f, 3f, 3f, 0f),
}; };
selector.OnOpenGuidebook += OnOpenGuidebook;
var icon = new TextureRect var icon = new TextureRect
{ {
@@ -867,7 +874,7 @@ namespace Content.Client.Lobby.UI
}; };
var jobIcon = _prototypeManager.Index(job.Icon); var jobIcon = _prototypeManager.Index(job.Icon);
icon.Texture = jobIcon.Icon.Frame0(); 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)) if (!_requirements.IsAllowed(job, out var reason))
{ {

View File

@@ -1,9 +1,14 @@
<BoxContainer xmlns="https://spacestation14.io" <BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Orientation="Horizontal"> Orientation="Horizontal">
<Label Name="TitleLabel" <Label Name="TitleLabel"
Margin="5 0" Margin="5 0"
MouseFilter="Stop"/> MouseFilter="Stop"/>
<BoxContainer Name="OptionsContainer"
SetWidth="400"/> <!--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> </BoxContainer>

View File

@@ -1,10 +1,12 @@
using System.Numerics; using System.Numerics;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Client.Lobby.UI.Roles; namespace Content.Client.Lobby.UI.Roles;
@@ -17,8 +19,10 @@ public sealed partial class RequirementsSelector : BoxContainer
{ {
private readonly RadioOptions<int> _options; private readonly RadioOptions<int> _options;
private readonly StripeBack _lockStripe; private readonly StripeBack _lockStripe;
private List<ProtoId<GuideEntryPrototype>>? _guides;
public event Action<int>? OnSelected; public event Action<int>? OnSelected;
public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
public int Selected => _options.SelectedId; public int Selected => _options.SelectedId;
@@ -60,18 +64,33 @@ public sealed partial class RequirementsSelector : BoxContainer
requirementsLabel requirementsLabel
} }
}; };
Help.OnPressed += _ =>
{
if (_guides != null)
OnOpenGuidebook?.Invoke(_guides);
};
} }
/// <summary> /// <summary>
/// Actually adds the controls. /// Actually adds the controls.
/// </summary> /// </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) foreach (var (text, value) in items)
{ {
_options.AddItem(Loc.GetString(text), value); _options.AddItem(Loc.GetString(text), value);
} }
Help.Visible = guides != null;
_guides = guides;
TitleLabel.Text = title; TitleLabel.Text = title;
TitleLabel.MinSize = new Vector2(titleSize, 0f); TitleLabel.MinSize = new Vector2(titleSize, 0f);
TitleLabel.ToolTip = description; TitleLabel.ToolTip = description;

View File

@@ -78,6 +78,8 @@ namespace Content.Client.Stylesheets
public const string StyleClassLabelSmall = "LabelSmall"; public const string StyleClassLabelSmall = "LabelSmall";
public const string StyleClassButtonBig = "ButtonBig"; public const string StyleClassButtonBig = "ButtonBig";
public const string StyleClassButtonHelp = "HelpButton";
public const string StyleClassPopupMessageSmall = "PopupMessageSmall"; public const string StyleClassPopupMessageSmall = "PopupMessageSmall";
public const string StyleClassPopupMessageSmallCaution = "PopupMessageSmallCaution"; public const string StyleClassPopupMessageSmallCaution = "PopupMessageSmallCaution";
public const string StyleClassPopupMessageMedium = "PopupMessageMedium"; 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}), 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 --- // Labels ---
Element<Label>().Class(StyleClassLabelBig) Element<Label>().Class(StyleClassLabelBig)
.Prop(Label.StylePropertyFont, notoSans16), .Prop(Label.StylePropertyFont, notoSans16),

View File

@@ -1,6 +1,7 @@
using System.Numerics; using System.Numerics;
using Content.Client.Guidebook; using Content.Client.Guidebook;
using Content.Client.Guidebook.Components; using Content.Client.Guidebook.Components;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
@@ -32,8 +33,8 @@ namespace Content.Client.UserInterface.Controls
set => WindowTitle.Text = value; set => WindowTitle.Text = value;
} }
private List<string>? _helpGuidebookIds; private List<ProtoId<GuideEntryPrototype>>? _helpGuidebookIds;
public List<string>? HelpGuidebookIds public List<ProtoId<GuideEntryPrototype>>? HelpGuidebookIds
{ {
get => _helpGuidebookIds; get => _helpGuidebookIds;
set set

View File

@@ -4,6 +4,7 @@ using Content.Client.Guidebook;
using Content.Client.Guidebook.Controls; using Content.Client.Guidebook.Controls;
using Content.Client.Lobby; using Content.Client.Lobby;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Guidebook;
using Content.Shared.Input; using Content.Shared.Input;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers; using Robust.Client.UserInterface.Controllers;
@@ -74,12 +75,12 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
public void OnSystemLoaded(GuidebookSystem system) public void OnSystemLoaded(GuidebookSystem system)
{ {
_guidebookSystem.OnGuidebookOpen += ToggleGuidebook; _guidebookSystem.OnGuidebookOpen += OpenGuidebook;
} }
public void OnSystemUnloaded(GuidebookSystem system) public void OnSystemUnloaded(GuidebookSystem system)
{ {
_guidebookSystem.OnGuidebookOpen -= ToggleGuidebook; _guidebookSystem.OnGuidebookOpen -= OpenGuidebook;
} }
internal void UnloadButton() internal void UnloadButton()
@@ -103,6 +104,22 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
ToggleGuidebook(); ToggleGuidebook();
} }
public void ToggleGuidebook()
{
if (_guideWindow == null)
return;
if (_guideWindow.IsOpen)
{
UIManager.ClickSound();
_guideWindow.Close();
}
else
{
OpenGuidebook();
}
}
private void OnWindowClosed() private void OnWindowClosed()
{ {
if (GuidebookButton != null) 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 /// <param name="includeChildren">Whether or not to automatically include child entries. If false, this will ONLY
/// show the specified entries</param> /// show the specified entries</param>
/// <param name="selected">The guide whose contents should be displayed when the guidebook is opened</param> /// <param name="selected">The guide whose contents should be displayed when the guidebook is opened</param>
public void ToggleGuidebook( public void OpenGuidebook(
Dictionary<string, GuideEntry>? guides = null, Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>? guides = null,
List<string>? rootEntries = null, List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
string? forceRoot = null, ProtoId<GuideEntryPrototype>? forceRoot = null,
bool includeChildren = true, bool includeChildren = true,
string? selected = null) ProtoId<GuideEntryPrototype>? selected = null)
{ {
if (_guideWindow == null) if (_guideWindow == null)
return; return;
if (_guideWindow.IsOpen)
{
UIManager.ClickSound();
_guideWindow.Close();
return;
}
if (GuidebookButton != null) if (GuidebookButton != null)
GuidebookButton.SetClickPressed(!_guideWindow.IsOpen); GuidebookButton.SetClickPressed(!_guideWindow.IsOpen);
if (guides == null) if (guides == null)
{ {
guides = _prototypeManager.EnumeratePrototypes<GuideEntryPrototype>() guides = _prototypeManager.EnumeratePrototypes<GuideEntryPrototype>()
.ToDictionary(x => x.ID, x => (GuideEntry) x); .ToDictionary(x => new ProtoId<GuideEntryPrototype>(x.ID), x => (GuideEntry) x);
} }
else if (includeChildren) else if (includeChildren)
{ {
@@ -171,17 +181,17 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
_guideWindow.OpenCenteredRight(); _guideWindow.OpenCenteredRight();
} }
public void ToggleGuidebook( public void OpenGuidebook(
List<string> guideList, List<ProtoId<GuideEntryPrototype>> guideList,
List<string>? rootEntries = null, List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
string? forceRoot = null, ProtoId<GuideEntryPrototype>? forceRoot = null,
bool includeChildren = true, 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) 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}"); Logger.Error($"Encountered unknown guide prototype: {guideId}");
continue; continue;
@@ -189,17 +199,29 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
guides.Add(guideId, guide); guides.Add(guideId, guide);
} }
ToggleGuidebook(guides, rootEntries, forceRoot, includeChildren, selected); OpenGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
} }
private void RecursivelyAddChildren(GuideEntry guide, Dictionary<string, GuideEntry> guides) public void CloseGuidebook()
{
if (_guideWindow == null)
return;
if (_guideWindow.IsOpen)
{
UIManager.ClickSound();
_guideWindow.Close();
}
}
private void RecursivelyAddChildren(GuideEntry guide, Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> guides)
{ {
foreach (var childId in guide.Children) foreach (var childId in guide.Children)
{ {
if (guides.ContainsKey(childId)) if (guides.ContainsKey(childId))
continue; 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."); 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; continue;

View File

@@ -1,9 +1,9 @@
using System.Globalization; using System.Globalization;
using Content.Client.Gameplay; using Content.Client.Gameplay;
using Content.Client.Guidebook;
using Content.Client.Info; using Content.Client.Info;
using Content.Shared.Administration.Managers; using Content.Shared.Administration.Managers;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Guidebook;
using Content.Shared.Info; using Content.Shared.Info;
using Robust.Client; using Robust.Client;
using Robust.Client.Console; using Robust.Client.Console;

View File

@@ -3,6 +3,7 @@ using Content.Client.Guidebook.Richtext;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using System.Linq; using System.Linq;
using Content.Shared.Guidebook;
namespace Content.IntegrationTests.Tests.Guidebook; namespace Content.IntegrationTests.Tests.Guidebook;

View File

@@ -67,7 +67,6 @@ namespace Content.Server.Entry
factory.RegisterIgnore(IgnoredComponents.List); factory.RegisterIgnore(IgnoredComponents.List);
prototypes.RegisterIgnore("parallax"); prototypes.RegisterIgnore("parallax");
prototypes.RegisterIgnore("guideEntry");
ServerContentIoC.Register(); ServerContentIoC.Register();

View File

@@ -1,8 +1,13 @@
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Robust.Shared.Utility; 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] [Virtual]
public class GuideEntry public class GuideEntry
@@ -10,7 +15,7 @@ public class GuideEntry
/// <summary> /// <summary>
/// The file containing the contents of this guide. /// The file containing the contents of this guide.
/// </summary> /// </summary>
[DataField("text", required: true)] public ResPath Text = default!; [DataField(required: true)] public ResPath Text = default!;
/// <summary> /// <summary>
/// The unique id for this guide. /// The unique id for this guide.
@@ -21,28 +26,22 @@ public class GuideEntry
/// <summary> /// <summary>
/// The name of this guide. This gets localized. /// The name of this guide. This gets localized.
/// </summary> /// </summary>
[DataField("name", required: true)] public string Name = default!; [DataField(required: true)] public string Name = default!;
/// <summary> /// <summary>
/// The "children" of this guide for when guides are shown in a tree / table of contents. /// The "children" of this guide for when guides are shown in a tree / table of contents.
/// </summary> /// </summary>
[DataField("children", customTypeSerializer:typeof(PrototypeIdListSerializer<GuideEntryPrototype>))] [DataField]
public List<string> Children = new(); public List<ProtoId<GuideEntryPrototype>> Children = new();
/// <summary> /// <summary>
/// Enable filtering of items. /// Enable filtering of items.
/// </summary> /// </summary>
[DataField("filterEnabled")] public bool FilterEnabled = default!; [DataField] public bool FilterEnabled = default!;
/// <summary> /// <summary>
/// Priority for sorting top-level guides when shown in a tree / table of contents. /// 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"/>. /// If the guide is the child of some other guide, the order simply determined by the order of children in <see cref="Children"/>.
/// </summary> /// </summary>
[DataField("priority")] public int Priority = 0; [DataField] public int Priority = 0;
}
[Prototype("guideEntry")]
public sealed partial class GuideEntryPrototype : GuideEntry, IPrototype
{
public string ID => Id;
} }

View File

@@ -1,3 +1,4 @@
using Content.Shared.Guidebook;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -43,4 +44,11 @@ public sealed partial class AntagPrototype : IPrototype
/// </summary> /// </summary>
[DataField("requirements")] [DataField("requirements")]
public HashSet<JobRequirement>? 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;
} }

View File

@@ -1,4 +1,5 @@
using Content.Shared.Access; using Content.Shared.Access;
using Content.Shared.Guidebook;
using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.StatusIcon; using Content.Shared.StatusIcon;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -117,6 +118,13 @@ namespace Content.Shared.Roles
[DataField] [DataField]
public bool Whitelisted; 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> /// <summary>

View File

@@ -28,7 +28,9 @@
name: guide-entry-chemist name: guide-entry-chemist
text: "/ServerInfo/Guidebook/Medical/Chemist.xml" text: "/ServerInfo/Guidebook/Medical/Chemist.xml"
children: children:
- Medicine # - Medicine
# Duplicate guide entries are currently not supported
# TODO GUIDEBOOK Maybe allow duplicate entries?
- Botanicals - Botanicals
- AdvancedBrute - AdvancedBrute

View File

@@ -4,6 +4,7 @@
antagonist: true antagonist: true
setPreference: false setPreference: false
objective: roles-antag-space-ninja-objective objective: roles-antag-space-ninja-objective
guides: [ SpaceNinja ]
#Ninja Gear #Ninja Gear
- type: startingGear - type: startingGear
@@ -33,4 +34,4 @@
- Screwdriver - Screwdriver
- Wirecutter - Wirecutter
- Welder - Welder
- Multitool - Multitool

View File

@@ -7,6 +7,7 @@
requirements: requirements:
- !type:OverallPlaytimeRequirement - !type:OverallPlaytimeRequirement
time: 18000 # 5h time: 18000 # 5h
guides: [ NuclearOperatives ]
- type: antag - type: antag
id: NukeopsMedic id: NukeopsMedic
@@ -18,8 +19,9 @@
- !type:OverallPlaytimeRequirement - !type:OverallPlaytimeRequirement
time: 18000 # 5h time: 18000 # 5h
- !type:RoleTimeRequirement - !type:RoleTimeRequirement
role: JobChemist role: JobChemist
time: 10800 # 3h time: 10800 # 3h
guides: [ NuclearOperatives ]
- type: antag - type: antag
id: NukeopsCommander id: NukeopsCommander
@@ -34,6 +36,7 @@
department: Security department: Security
time: 18000 # 5h time: 18000 # 5h
# should be changed to nukie playtime when thats tracked (wyci) # should be changed to nukie playtime when thats tracked (wyci)
guides: [ NuclearOperatives ]
#Nuclear Operative Gear #Nuclear Operative Gear
- type: startingGear - type: startingGear
@@ -96,4 +99,4 @@
id: SyndicateLoneOperativeGearFull id: SyndicateLoneOperativeGearFull
parent: SyndicateOperativeGearFull parent: SyndicateOperativeGearFull
equipment: equipment:
pocket2: BaseUplinkRadio60TC pocket2: BaseUplinkRadio60TC

View File

@@ -4,6 +4,7 @@
antagonist: true antagonist: true
setPreference: true setPreference: true
objective: roles-antag-rev-head-objective objective: roles-antag-rev-head-objective
guides: [ Revolutionaries ]
- type: antag - type: antag
id: Rev id: Rev
@@ -11,10 +12,11 @@
antagonist: true antagonist: true
setPreference: false setPreference: false
objective: roles-antag-rev-objective objective: roles-antag-rev-objective
guides: [ Revolutionaries ]
- type: startingGear - type: startingGear
id: HeadRevGear id: HeadRevGear
storage: storage:
back: back:
- Flash - Flash
- ClothingEyesGlassesSunglasses - ClothingEyesGlassesSunglasses

View File

@@ -4,6 +4,7 @@
antagonist: true antagonist: true
setPreference: true setPreference: true
objective: roles-antag-syndicate-agent-objective objective: roles-antag-syndicate-agent-objective
guides: [ Traitors ]
# Syndicate Operative Outfit - Monkey # Syndicate Operative Outfit - Monkey
- type: startingGear - type: startingGear
@@ -36,4 +37,4 @@
ears: ClothingHeadsetAltSyndicate ears: ClothingHeadsetAltSyndicate
gloves: ClothingHandsGlovesCombat gloves: ClothingHandsGlovesCombat
pocket1: BaseUplinkRadio40TC pocket1: BaseUplinkRadio40TC
id: SyndiPDA id: SyndiPDA

View File

@@ -4,6 +4,7 @@
antagonist: true antagonist: true
setPreference: true setPreference: true
objective: roles-antag-initial-infected-objective objective: roles-antag-initial-infected-objective
guides: [ Zombies ]
- type: antag - type: antag
id: Zombie id: Zombie
@@ -11,3 +12,4 @@
antagonist: true antagonist: true
setPreference: false setPreference: false
objective: roles-antag-zombie-objective objective: roles-antag-zombie-objective
guides: [ Zombies ]